feat: end-point for deal document generation
This commit is contained in:
20
constants.py
20
constants.py
@@ -1,5 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jinja2 import FileSystemLoader, Environment
|
||||||
|
|
||||||
|
from utils.code128 import encode128
|
||||||
|
|
||||||
|
ENV = Environment(loader=FileSystemLoader(Path("templates") / Path("documents")))
|
||||||
|
ENV.globals['now'] = datetime.now
|
||||||
|
ENV.globals['encode128'] = encode128
|
||||||
|
|
||||||
APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
|
APP_PATH = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
|
||||||
|
|
||||||
@@ -29,3 +39,13 @@ MONTHS = (
|
|||||||
'ноября',
|
'ноября',
|
||||||
'декабря'
|
'декабря'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DEAL_STATUS_STR = [
|
||||||
|
'Создан',
|
||||||
|
'Ожидает принятия',
|
||||||
|
'Упаковка',
|
||||||
|
'Ожидание отправки',
|
||||||
|
'Ожидание оплаты',
|
||||||
|
'Завершено',
|
||||||
|
'Отменено',
|
||||||
|
]
|
||||||
|
|||||||
@@ -189,6 +189,20 @@ async def get_deal_document(
|
|||||||
pdf_file: BytesIO = await BillingService(session).create_billing_document_pdf(deal_id)
|
pdf_file: BytesIO = await BillingService(session).create_billing_document_pdf(deal_id)
|
||||||
return Response(pdf_file.getvalue(), media_type='application/pdf')
|
return Response(pdf_file.getvalue(), media_type='application/pdf')
|
||||||
|
|
||||||
|
|
||||||
|
@deal_router.get(
|
||||||
|
'/detailedDocument/{deal_id}',
|
||||||
|
operation_id='get_deal_document_detailed',
|
||||||
|
# dependencies=[Depends(authorized_user)],
|
||||||
|
)
|
||||||
|
async def get_detailed_deal_document(
|
||||||
|
deal_id: int,
|
||||||
|
session: Annotated[AsyncSession, Depends(get_session)],
|
||||||
|
):
|
||||||
|
pdf_file: BytesIO = await DealService(session).create_detailed_deal_document_pdf(deal_id)
|
||||||
|
return Response(pdf_file.getvalue(), media_type='application/pdf')
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Deal services
|
# region Deal services
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from starlette import status
|
|||||||
from weasyprint import HTML
|
from weasyprint import HTML
|
||||||
|
|
||||||
import backend.config
|
import backend.config
|
||||||
from constants import MONTHS
|
from constants import MONTHS, ENV
|
||||||
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
|
from external.billing import BillingClient, CreateBillingRequestValue, CreateBillRequestSchema, CreateBillRequestItems, \
|
||||||
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema
|
BillStatusUpdateRequest, NotificationChannel, NotifyReceivedBillRequestSchema, DeleteBillRequestSchema
|
||||||
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel
|
from models import DealBillRequest, Deal, DealProduct, DealService as DealServiceModel
|
||||||
@@ -20,8 +20,6 @@ from services.base import BaseService
|
|||||||
from services.deal import DealService
|
from services.deal import DealService
|
||||||
from utils.list_utils import to_locale_number
|
from utils.list_utils import to_locale_number
|
||||||
|
|
||||||
env = Environment(loader=FileSystemLoader(Path("templates") / Path("documents")))
|
|
||||||
|
|
||||||
|
|
||||||
class BillingService(BaseService):
|
class BillingService(BaseService):
|
||||||
async def _process_update_details(
|
async def _process_update_details(
|
||||||
@@ -182,7 +180,7 @@ class BillingService(BaseService):
|
|||||||
deal_price = sum((service.price * service.amount for service in services))
|
deal_price = sum((service.price * service.amount for service in services))
|
||||||
deal_price_words = get_string_by_number(deal_price)[0:-10]
|
deal_price_words = get_string_by_number(deal_price)[0:-10]
|
||||||
deal_price = to_locale_number(deal_price)
|
deal_price = to_locale_number(deal_price)
|
||||||
template = env.get_template("bill-of-payment.html")
|
template = ENV.get_template("bill-of-payment.html")
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
curr_date = f"{now.day} {MONTHS[now.month - 1]} {now.year} г."
|
curr_date = f"{now.day} {MONTHS[now.month - 1]} {now.year} г."
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import lexorank
|
import lexorank
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy import select, func, update, delete, insert
|
from sqlalchemy import select, func, update, delete, insert
|
||||||
from sqlalchemy.orm import joinedload, selectinload
|
from sqlalchemy.orm import joinedload, selectinload
|
||||||
from starlette import status
|
from starlette import status
|
||||||
|
from weasyprint import HTML, CSS
|
||||||
|
|
||||||
|
import constants
|
||||||
import models.deal
|
import models.deal
|
||||||
import models.secondary
|
import models.secondary
|
||||||
from models import User, Service, Client, DealProduct
|
from constants import ENV, DEAL_STATUS_STR
|
||||||
|
from models import User, Service, Client, DealProduct, DealService as DealServiceModel, Product
|
||||||
from models.deal import *
|
from models.deal import *
|
||||||
from schemas.client import ClientDetailsSchema
|
from schemas.client import ClientDetailsSchema
|
||||||
from schemas.deal import *
|
from schemas.deal import *
|
||||||
@@ -15,6 +20,7 @@ from services.base import BaseService
|
|||||||
from services.client import ClientService
|
from services.client import ClientService
|
||||||
from services.service import ServiceService
|
from services.service import ServiceService
|
||||||
from services.shipping_warehouse import ShippingWarehouseService
|
from services.shipping_warehouse import ShippingWarehouseService
|
||||||
|
from utils.images_fetcher import fetch_images
|
||||||
|
|
||||||
|
|
||||||
class DealService(BaseService):
|
class DealService(BaseService):
|
||||||
@@ -987,3 +993,56 @@ class DealService(BaseService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await self.session.rollback()
|
await self.session.rollback()
|
||||||
return DealCompleteResponse(ok=False, message=str(e))
|
return DealCompleteResponse(ok=False, message=str(e))
|
||||||
|
|
||||||
|
# region Deal documents
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _get_product_services_totals(deal: Deal) -> List[int]:
|
||||||
|
totals: List[int] = []
|
||||||
|
|
||||||
|
for product in deal.products:
|
||||||
|
total = sum((service.price * product.quantity for service in product.services))
|
||||||
|
totals.append(total)
|
||||||
|
|
||||||
|
return totals
|
||||||
|
|
||||||
|
async def _create_detailed_deal_document_html(self, deal_id: int):
|
||||||
|
deal: Deal | None = await self.session.scalar(
|
||||||
|
select(Deal)
|
||||||
|
.where(Deal.id == deal_id)
|
||||||
|
.options(
|
||||||
|
selectinload(Deal.products).selectinload(DealProduct.services),
|
||||||
|
selectinload(Deal.products).selectinload(DealProduct.product).selectinload(Product.barcodes),
|
||||||
|
selectinload(Deal.services).selectinload(DealServiceModel.service),
|
||||||
|
selectinload(Deal.status_history),
|
||||||
|
joinedload(Deal.client),
|
||||||
|
joinedload(Deal.shipping_warehouse),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not deal:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
last_status: DealStatusHistory = max(deal.status_history, key=lambda status: status.changed_at)
|
||||||
|
general_services_total = sum((service.price * service.quantity for service in deal.services))
|
||||||
|
product_services_totals = await self._get_product_services_totals(deal)
|
||||||
|
template = ENV.get_template("deal.html")
|
||||||
|
product_images = await fetch_images([product.product.images[0].image_url for product in deal.products])
|
||||||
|
|
||||||
|
result = template.render({
|
||||||
|
"general_services_total": general_services_total,
|
||||||
|
"product_services_totals": product_services_totals,
|
||||||
|
"deal": deal,
|
||||||
|
"current_status_str": DEAL_STATUS_STR[deal.current_status],
|
||||||
|
"last_status": last_status,
|
||||||
|
"product_images": product_images,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def create_detailed_deal_document_pdf(self, deal_id) -> BytesIO:
|
||||||
|
doc = await self._create_detailed_deal_document_html(deal_id)
|
||||||
|
pdf_file = BytesIO()
|
||||||
|
HTML(string=doc).write_pdf(pdf_file, stylesheets=[CSS(constants.APP_PATH + '/static/css/deal.css')])
|
||||||
|
return pdf_file
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|||||||
167
static/css/deal.css
Normal file
167
static/css/deal.css
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
@media print {
|
||||||
|
#header,
|
||||||
|
#footer,
|
||||||
|
#nav {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
background-color: black;
|
||||||
|
height: 2px;
|
||||||
|
border: none;
|
||||||
|
margin-left: -100px;
|
||||||
|
margin-right: -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-container {
|
||||||
|
margin: -30px 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-name-id-container {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deal-id {
|
||||||
|
border: black solid 2px;
|
||||||
|
padding: 3px 2px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-text {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium-text {
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large-text {
|
||||||
|
font-size: 21px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-data {
|
||||||
|
height: 160px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-data > * {
|
||||||
|
width: 170px;
|
||||||
|
padding-right: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: solid gray 2px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot {
|
||||||
|
display: table-footer-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(5) {
|
||||||
|
border-right: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-child(odd) {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding: 10px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child, th:first-child {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-child, th:last-child {
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child, th:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:first-child), th:not(:first-child) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:first-child) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tfoot {
|
||||||
|
border-top: 2px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-container {
|
||||||
|
margin: -30px 30px -45px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img {
|
||||||
|
height: 300px;
|
||||||
|
width: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: black solid 2px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-container {
|
||||||
|
margin: 15px 0 -5px 385px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode {
|
||||||
|
font-family: "Libre Barcode 128", cursive;
|
||||||
|
font-size: 30pt;
|
||||||
|
transform: scale(1, 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-text {
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
2
static/js/JsBarcode.all.min.js
vendored
Normal file
2
static/js/JsBarcode.all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
162
templates/documents/deal.html
Normal file
162
templates/documents/deal.html
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<script src="/static/js/JsBarcode.all.min.js"></script>
|
||||||
|
<title>Сделка</title>
|
||||||
|
<style>
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Libre+Barcode+128");
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Barlow+Condensed:300,400,700");
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
JsBarcode(".barcode").init();
|
||||||
|
</script>
|
||||||
|
<div class="doc-container">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<!--#region Header -->
|
||||||
|
<div class="deal-name-id-container">
|
||||||
|
<div class="medium-text bold">
|
||||||
|
Название сделки: {{ deal.name }}
|
||||||
|
</div>
|
||||||
|
<div class="medium-text deal-id">
|
||||||
|
ID:{{ deal.id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div class="medium-text">
|
||||||
|
Дата создания: {{ now().strftime("%d.%m.%Y, %H:%M") }}
|
||||||
|
</div>
|
||||||
|
<div class="medium-text">
|
||||||
|
Текущий статус: {{ current_status_str }}
|
||||||
|
</div>
|
||||||
|
<div class="medium-text">
|
||||||
|
{% if deal.comment %}
|
||||||
|
Комментарий к сделке: {{ deal.comment }}
|
||||||
|
{% else %}
|
||||||
|
Комментарий к сделке отсутствует
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<!--#endregion -->
|
||||||
|
<!--#region General services -->
|
||||||
|
<div class="large-text bold">
|
||||||
|
Клиент: {{ deal.client.name }}
|
||||||
|
</div>
|
||||||
|
<div class="medium-text bold">
|
||||||
|
Дата отгрузки: {{ last_status.next_status_deadline.strftime("%d.%m.%Y") }}
|
||||||
|
</div>
|
||||||
|
{% if deal.base_marketplace.name %}
|
||||||
|
<div class="medium-text bold">Маркетплейс: {{ deal.base_marketplace.name }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if deal.shipping_warehouse.name %}
|
||||||
|
<div class="medium-text bold">Склад отгрузки: {{ deal.shipping_warehouse.name }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Общие услуги</th>
|
||||||
|
<th>Количество</th>
|
||||||
|
<th>Сумма</th>
|
||||||
|
</tr>
|
||||||
|
<tfoot>
|
||||||
|
</tfoot>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in deal.services %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ service.service.name }}</td>
|
||||||
|
<td>{{ '{:,}'.format(service.quantity) }} шт.</td>
|
||||||
|
<td>{{ '{:,}'.format(service.price * service.quantity).replace(',', ' ') }} Р</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="medium-text total align-right bold">
|
||||||
|
Итого: {{ general_services_total }} Р
|
||||||
|
</div>
|
||||||
|
<!--#endregion -->
|
||||||
|
<!--#region Product services -->
|
||||||
|
{% for product in deal.products %}
|
||||||
|
<div style="page-break-inside: avoid">
|
||||||
|
<hr/>
|
||||||
|
<div style="display: flex">
|
||||||
|
{% if product.product.images|length != 0 %}
|
||||||
|
<div class="img-container">
|
||||||
|
<img class="product-img" src="data:image/jpeg;base64,{{ product_images[loop.index0] }}"
|
||||||
|
alt="Фото товара"/>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div style="margin: 12px 0">
|
||||||
|
<div class="medium-text bold">
|
||||||
|
{{ loop.index }}. {{ product.product.name }}
|
||||||
|
</div>
|
||||||
|
<div class="product-data">
|
||||||
|
<div class="small-text">Артикул: {{ product.product.article }}</div>
|
||||||
|
{% if product.product.brand %}
|
||||||
|
<div class="small-text">Бренд: {{ product.product.brand }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if product.product.color %}
|
||||||
|
<div class="small-text">Цвет: {{ product.product.color }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if product.product.size %}
|
||||||
|
<div class="small-text">Размер: {{ product.product.size }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if product.quantity %}
|
||||||
|
<div class="small-text">Количество: {{ product.quantity }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if product.product.additional_info %}
|
||||||
|
<div class="small-text">
|
||||||
|
Доп. информация: {{ product.product.additional_info }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div style="text-align: right">
|
||||||
|
<div class="barcode-container">
|
||||||
|
<div class="barcode">
|
||||||
|
{{ encode128(product.product.barcodes[0].barcode, "A") }}
|
||||||
|
</div>
|
||||||
|
<div class="barcode-text">
|
||||||
|
{{ product.product.barcodes[0].barcode }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Наименование услуги</th>
|
||||||
|
<th>Цена</th>
|
||||||
|
<th>Сумма</th>
|
||||||
|
</tr>
|
||||||
|
<tfoot>
|
||||||
|
</tfoot>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for service in product.services %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ service.service.name }}</td>
|
||||||
|
<td>{{ '{:,}'.format(product.quantity) }} Р</td>
|
||||||
|
<td>{{ '{:,}'.format(service.price * product.quantity).replace(',', ' ') }} Р</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="medium-text total align-right bold">
|
||||||
|
Итого: {{ product_services_totals[loop.index0] }} Р
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<!--#endregion -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
utils/code128.py
Normal file
35
utils/code128.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
def to_set_c(text):
|
||||||
|
# Match pairs of digits and map them to the appropriate ASCII characters
|
||||||
|
return ''.join([
|
||||||
|
chr(int(ascii_code) + 100 if int(ascii_code) > 94 else int(ascii_code) + 32)
|
||||||
|
for ascii_code in re.findall(r'\d{2}', text)
|
||||||
|
])
|
||||||
|
|
||||||
|
def check_sum_128(data, start_code):
|
||||||
|
sum_value = start_code
|
||||||
|
for i, char in enumerate(data):
|
||||||
|
code = ord(char)
|
||||||
|
value = code - 100 if code > 199 else code - 32
|
||||||
|
sum_value += (i + 1) * value
|
||||||
|
|
||||||
|
checksum = (sum_value % 103) + 32
|
||||||
|
if checksum > 126:
|
||||||
|
checksum += 68
|
||||||
|
return chr(checksum)
|
||||||
|
|
||||||
|
def encode128(text, code_abc="B"):
|
||||||
|
start_code = chr(ord(code_abc.upper()) + 138)
|
||||||
|
stop = chr(206)
|
||||||
|
|
||||||
|
if code_abc.upper() == 'C':
|
||||||
|
text = to_set_c(text)
|
||||||
|
|
||||||
|
check = check_sum_128(text, ord(start_code) - 100)
|
||||||
|
|
||||||
|
# Replace spaces with ASCII 194
|
||||||
|
text = text.replace(" ", chr(194))
|
||||||
|
|
||||||
|
return start_code + text + check + stop
|
||||||
|
|
||||||
20
utils/images_fetcher.py
Normal file
20
utils/images_fetcher.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_image(url: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as response:
|
||||||
|
if response.status == 200:
|
||||||
|
image_data = await response.read()
|
||||||
|
return base64.b64encode(image_data).decode("utf-8")
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_images(urls: List[str]) -> Tuple[str]:
|
||||||
|
tasks = [fetch_image(url) for url in urls]
|
||||||
|
return await asyncio.gather(*tasks)
|
||||||
Reference in New Issue
Block a user