Compare commits
31 Commits
5dd9e19877
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c3c5cfc60 | ||
|
|
85df411724 | ||
|
|
f0fd0efb7f | ||
|
|
6a4de725b5 | ||
|
|
71839b0ccf | ||
|
|
7bf20bafa8 | ||
|
|
76bc1c2302 | ||
|
|
46b369ecd9 | ||
|
|
74bf1474e2 | ||
|
|
61710a0347 | ||
|
|
e970bb683a | ||
|
|
c27c7598b5 | ||
| b4b4fa7643 | |||
| 60812652cf | |||
| cb0235e19f | |||
|
|
5c356f5802 | ||
|
|
ff19448991 | ||
| 5e413b11e0 | |||
|
|
3cfa60507e | ||
|
|
6679107b13 | ||
|
|
20eba7f183 | ||
|
|
c6d75831c9 | ||
|
|
b2e2d02818 | ||
|
|
8cb7893aff | ||
|
|
015e09a980 | ||
|
|
a70ab5d3cb | ||
|
|
9d5dbd80cc | ||
|
|
1c6e810ec1 | ||
|
|
8352097ffb | ||
|
|
0e48d1bbaa | ||
| 8c6b25ded8 |
@@ -24,3 +24,6 @@
|
|||||||
### Show on cascade deletion
|
### Show on cascade deletion
|
||||||
|
|
||||||
## Update contract after (without registration)
|
## Update contract after (without registration)
|
||||||
|
|
||||||
|
## Preview form (if not visible can be accessed by referer nothing is stored)
|
||||||
|
## View and edit contract application (dashboard/contracts/id/edit/)
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ hatch run pytest
|
|||||||
hatch run pytest --cov=src -vv
|
hatch run pytest --cov=src -vv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Autoformat
|
||||||
|
```console
|
||||||
|
find -type f -name '*.py' ! -path 'alembic/*' -exec autopep8 --in-place --aggressive --aggressive '{}' \;
|
||||||
|
pylint -d R0801,R0903,W0511,W0603,C0103,R0902 .
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
`backend` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
`backend` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ target_metadata = SQLModel.metadata
|
|||||||
|
|
||||||
# other values from the config, defined by the needs of env.py,
|
# other values from the config, defined by the needs of env.py,
|
||||||
# can be acquired:
|
# can be acquired:
|
||||||
config.set_main_option("sqlalchemy.url", f'postgresql://{settings.db_user}:{settings.db_pass}@{settings.db_host}:5432/{settings.db_name}')
|
config.set_main_option(
|
||||||
|
"sqlalchemy.url", f'postgresql://{settings.db_user}:{settings.db_pass}@{settings.db_host}:5432/{settings.db_name}')
|
||||||
# ... etc.
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.add_column('paymentmethod', sa.Column('max', sa.Integer(), nullable=True))
|
op.add_column(
|
||||||
|
'paymentmethod',
|
||||||
|
sa.Column(
|
||||||
|
'max',
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=True))
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,28 +22,32 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('contracttype',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
'contracttype',
|
||||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column(
|
||||||
sa.PrimaryKeyConstraint('id')
|
'id',
|
||||||
)
|
sa.Integer(),
|
||||||
op.create_table('productor',
|
nullable=False),
|
||||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column(
|
||||||
sa.Column('address', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
'name',
|
||||||
sa.Column('type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sqlmodel.sql.sqltypes.AutoString(),
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id'))
|
||||||
)
|
op.create_table(
|
||||||
|
'productor', sa.Column(
|
||||||
|
'name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column(
|
||||||
|
'address', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column(
|
||||||
|
'type', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column(
|
||||||
|
'id', sa.Integer(), nullable=False), sa.PrimaryKeyConstraint('id'))
|
||||||
op.create_table('template',
|
op.create_table('template',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('user',
|
op.create_table(
|
||||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
'user', sa.Column(
|
||||||
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
'name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
'email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), sa.Column(
|
||||||
sa.PrimaryKeyConstraint('id')
|
'id', sa.Integer(), nullable=False), sa.PrimaryKeyConstraint('id'))
|
||||||
)
|
|
||||||
op.create_table('form',
|
op.create_table('form',
|
||||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('productor_id', sa.Integer(), nullable=True),
|
sa.Column('productor_id', sa.Integer(), nullable=True),
|
||||||
@@ -78,13 +82,13 @@ def upgrade() -> None:
|
|||||||
sa.ForeignKeyConstraint(['productor_id'], ['productor.id'], ),
|
sa.ForeignKeyConstraint(['productor_id'], ['productor.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('usercontracttypelink',
|
op.create_table(
|
||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
'usercontracttypelink', sa.Column(
|
||||||
sa.Column('contract_type_id', sa.Integer(), nullable=False),
|
'user_id', sa.Integer(), nullable=False), sa.Column(
|
||||||
sa.ForeignKeyConstraint(['contract_type_id'], ['contracttype.id'], ),
|
'contract_type_id', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
['contract_type_id'], ['contracttype.id'], ), sa.ForeignKeyConstraint(
|
||||||
sa.PrimaryKeyConstraint('user_id', 'contract_type_id')
|
['user_id'], ['user.id'], ), sa.PrimaryKeyConstraint(
|
||||||
)
|
'user_id', 'contract_type_id'))
|
||||||
op.create_table('contract',
|
op.create_table('contract',
|
||||||
sa.Column('firstname', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('firstname', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
sa.Column('lastname', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
sa.Column('lastname', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.add_column('form', sa.Column('visible', sa.Boolean(), nullable=False, default=False, server_default="False"))
|
op.add_column(
|
||||||
|
'form',
|
||||||
|
sa.Column(
|
||||||
|
'visible',
|
||||||
|
sa.Boolean(),
|
||||||
|
nullable=False,
|
||||||
|
default=False,
|
||||||
|
server_default="False"))
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ dependencies = [
|
|||||||
"pytest",
|
"pytest",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"pytest-mock",
|
"pytest-mock",
|
||||||
|
"pylint",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
alembic==1.18.4
|
||||||
|
annotated-doc==0.0.4
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.12.1
|
||||||
|
astroid==4.0.4
|
||||||
|
autopep8==2.3.2
|
||||||
|
brotli==1.2.0
|
||||||
|
certifi==2026.2.25
|
||||||
|
cffi==2.0.0
|
||||||
|
charset-normalizer==3.4.4
|
||||||
|
click==8.3.1
|
||||||
|
coverage==7.13.4
|
||||||
|
cryptography==46.0.5
|
||||||
|
cssselect2==0.9.0
|
||||||
|
dill==0.4.1
|
||||||
|
dnspython==2.8.0
|
||||||
|
email-validator==2.3.0
|
||||||
|
fastapi==0.135.1
|
||||||
|
fastapi-cli==0.0.24
|
||||||
|
fastapi-cloud-cli==0.14.0
|
||||||
|
fastar==0.8.0
|
||||||
|
fonttools==4.61.1
|
||||||
|
greenlet==3.3.2
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httptools==0.7.1
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
iniconfig==2.3.0
|
||||||
|
isort==8.0.1
|
||||||
|
Jinja2==3.1.6
|
||||||
|
lxml==6.0.2
|
||||||
|
Mako==1.3.10
|
||||||
|
markdown-it-py==4.0.0
|
||||||
|
MarkupSafe==3.0.3
|
||||||
|
mccabe==0.7.0
|
||||||
|
mdurl==0.1.2
|
||||||
|
odfdo==3.21.0
|
||||||
|
packaging==26.0
|
||||||
|
pillow==12.1.1
|
||||||
|
platformdirs==4.9.2
|
||||||
|
pluggy==1.6.0
|
||||||
|
prek==0.3.4
|
||||||
|
psycopg2-binary==2.9.11
|
||||||
|
pycodestyle==2.14.0
|
||||||
|
pycparser==3.0
|
||||||
|
pydantic==2.12.5
|
||||||
|
pydantic-extra-types==2.11.0
|
||||||
|
pydantic-settings==2.13.1
|
||||||
|
pydantic_core==2.41.5
|
||||||
|
pydyf==0.12.1
|
||||||
|
Pygments==2.19.2
|
||||||
|
PyJWT==2.11.0
|
||||||
|
pylint==4.0.5
|
||||||
|
pyphen==0.17.2
|
||||||
|
pytest==9.0.2
|
||||||
|
pytest-cov==7.0.0
|
||||||
|
pytest-mock==3.15.1
|
||||||
|
python-dotenv==1.2.2
|
||||||
|
python-multipart==0.0.22
|
||||||
|
PyYAML==6.0.3
|
||||||
|
requests==2.32.5
|
||||||
|
rich==14.3.3
|
||||||
|
rich-toolkit==0.19.7
|
||||||
|
rignore==0.7.6
|
||||||
|
sentry-sdk==2.53.0
|
||||||
|
shellingham==1.5.4
|
||||||
|
SQLAlchemy==2.0.47
|
||||||
|
sqlmodel==0.0.37
|
||||||
|
starlette==0.52.1
|
||||||
|
tinycss2==1.5.1
|
||||||
|
tinyhtml5==2.0.0
|
||||||
|
tomlkit==0.14.0
|
||||||
|
typer==0.24.1
|
||||||
|
typing-inspection==0.4.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.6.3
|
||||||
|
uvicorn==0.41.0
|
||||||
|
uvloop==0.22.1
|
||||||
|
watchfiles==1.1.1
|
||||||
|
weasyprint==68.1
|
||||||
|
webencodings==0.5.1
|
||||||
|
websockets==16.0
|
||||||
|
zopfli==0.4.1
|
||||||
|
|||||||
0
backend/src/auth/__init__.py
Normal file
0
backend/src/auth/__init__.py
Normal file
@@ -1,26 +1,27 @@
|
|||||||
from typing import Annotated
|
|
||||||
from fastapi import APIRouter, Security, HTTPException, Depends, Request, Cookie
|
|
||||||
from fastapi.responses import RedirectResponse, Response
|
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
import jwt
|
|
||||||
from jwt import PyJWKClient
|
|
||||||
|
|
||||||
from src.settings import AUTH_URL, TOKEN_URL, JWKS_URL, ISSUER, LOGOUT_URL, settings
|
|
||||||
import src.users.service as service
|
|
||||||
from src.database import get_session
|
|
||||||
from src.models import UserCreate, User, UserPublic
|
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import requests
|
from typing import Annotated
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
import src.messages as messages
|
|
||||||
|
import jwt
|
||||||
|
import requests
|
||||||
|
import src.users.service as service
|
||||||
|
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request
|
||||||
|
from fastapi.responses import RedirectResponse, Response
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
|
from jwt import PyJWKClient
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from src import messages
|
||||||
|
from src.database import get_session
|
||||||
|
from src.models import User, UserCreate, UserPublic
|
||||||
|
from src.settings import (AUTH_URL, ISSUER, JWKS_URL, LOGOUT_URL, TOKEN_URL,
|
||||||
|
settings)
|
||||||
|
|
||||||
router = APIRouter(prefix='/auth')
|
router = APIRouter(prefix='/auth')
|
||||||
|
|
||||||
jwk_client = PyJWKClient(JWKS_URL)
|
jwk_client = PyJWKClient(JWKS_URL)
|
||||||
security = HTTPBearer()
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
@router.get('/logout')
|
@router.get('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
params = {
|
params = {
|
||||||
@@ -59,9 +60,11 @@ def login():
|
|||||||
'redirect_uri': settings.keycloak_redirect_uri,
|
'redirect_uri': settings.keycloak_redirect_uri,
|
||||||
'state': state,
|
'state': state,
|
||||||
}
|
}
|
||||||
request_url = requests.Request('GET', AUTH_URL, params=params).prepare().url
|
request_url = requests.Request(
|
||||||
|
'GET', AUTH_URL, params=params).prepare().url
|
||||||
return RedirectResponse(request_url)
|
return RedirectResponse(request_url)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/callback')
|
@router.get('/callback')
|
||||||
def callback(code: str, session: Session = Depends(get_session)):
|
def callback(code: str, session: Session = Depends(get_session)):
|
||||||
data = {
|
data = {
|
||||||
@@ -74,7 +77,18 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
response = requests.post(TOKEN_URL, data=data, headers=headers)
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
TOKEN_URL,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
except requests.exceptions.Timeout as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('token')
|
||||||
|
) from error
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
@@ -85,7 +99,9 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
id_token = token_data['id_token']
|
id_token = token_data['id_token']
|
||||||
decoded_token = jwt.decode(id_token, options={'verify_signature': False})
|
decoded_token = jwt.decode(id_token, options={'verify_signature': False})
|
||||||
decoded_access_token = jwt.decode(token_data['access_token'], options={'verify_signature': False})
|
decoded_access_token = jwt.decode(
|
||||||
|
token_data['access_token'], options={
|
||||||
|
'verify_signature': False})
|
||||||
resource_access = decoded_access_token.get('resource_access')
|
resource_access = decoded_access_token.get('resource_access')
|
||||||
if not resource_access:
|
if not resource_access:
|
||||||
data = {
|
data = {
|
||||||
@@ -93,7 +109,13 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
'client_secret': settings.keycloak_client_secret,
|
'client_secret': settings.keycloak_client_secret,
|
||||||
'refresh_token': token_data['refresh_token'],
|
'refresh_token': token_data['refresh_token'],
|
||||||
}
|
}
|
||||||
res = requests.post(LOGOUT_URL, data=data)
|
try:
|
||||||
|
requests.post(LOGOUT_URL, data=data, timeout=10)
|
||||||
|
except requests.exceptions.Timeout as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('token')
|
||||||
|
) from error
|
||||||
resp = RedirectResponse(f'{settings.origins}?userNotAllowed=true')
|
resp = RedirectResponse(f'{settings.origins}?userNotAllowed=true')
|
||||||
return resp
|
return resp
|
||||||
roles = resource_access.get(settings.keycloak_client_id)
|
roles = resource_access.get(settings.keycloak_client_id)
|
||||||
@@ -103,7 +125,13 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
'client_secret': settings.keycloak_client_secret,
|
'client_secret': settings.keycloak_client_secret,
|
||||||
'refresh_token': token_data['refresh_token'],
|
'refresh_token': token_data['refresh_token'],
|
||||||
}
|
}
|
||||||
res = requests.post(LOGOUT_URL, data=data)
|
try:
|
||||||
|
requests.post(LOGOUT_URL, data=data, timeout=10)
|
||||||
|
except requests.exceptions.Timeout as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('token')
|
||||||
|
) from error
|
||||||
resp = RedirectResponse(f'{settings.origins}?userNotAllowed=true')
|
resp = RedirectResponse(f'{settings.origins}?userNotAllowed=true')
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@@ -141,6 +169,7 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def verify_token(token: str):
|
def verify_token(token: str):
|
||||||
try:
|
try:
|
||||||
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
||||||
@@ -153,31 +182,52 @@ def verify_token(token: str):
|
|||||||
leeway=60,
|
leeway=60,
|
||||||
)
|
)
|
||||||
return decoded
|
return decoded
|
||||||
except jwt.ExpiredSignatureError:
|
except jwt.ExpiredSignatureError as error:
|
||||||
raise HTTPException(status_code=401, detail=messages.Messages.tokenexipired)
|
raise HTTPException(
|
||||||
except jwt.InvalidTokenError:
|
status_code=401,
|
||||||
raise HTTPException(status_code=401, detail=messages.Messages.invalidtoken)
|
detail=messages.Messages.tokenexipired
|
||||||
|
) from error
|
||||||
|
except jwt.InvalidTokenError as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail=messages.Messages.invalidtoken
|
||||||
|
) from error
|
||||||
|
|
||||||
|
|
||||||
def get_current_user(request: Request, session: Session = Depends(get_session)):
|
def get_current_user(
|
||||||
|
request: Request,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
access_token = request.cookies.get('access_token')
|
access_token = request.cookies.get('access_token')
|
||||||
if not access_token:
|
if not access_token:
|
||||||
raise HTTPException(status_code=401, detail=messages.Messages.notauthenticated)
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail=messages.Messages.notauthenticated
|
||||||
|
)
|
||||||
payload = verify_token(access_token)
|
payload = verify_token(access_token)
|
||||||
if not payload:
|
if not payload:
|
||||||
raise HTTPException(status_code=401, detail='aze')
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail='aze'
|
||||||
|
)
|
||||||
email = payload.get('email')
|
email = payload.get('email')
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
raise HTTPException(status_code=401, detail=messages.Messages.notauthenticated)
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail=messages.Messages.notauthenticated
|
||||||
|
)
|
||||||
|
|
||||||
user = session.exec(select(User).where(User.email == email)).first()
|
user = session.exec(select(User).where(User.email == email)).first()
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=401, detail=messages.Messages.not_found('user'))
|
raise HTTPException(
|
||||||
|
status_code=401,
|
||||||
|
detail=messages.Messages.not_found('user')
|
||||||
|
)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
@router.post('/refresh')
|
@router.post('/refresh')
|
||||||
def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
def refresh_user_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
||||||
refresh = refresh_token
|
refresh = refresh_token
|
||||||
data = {
|
data = {
|
||||||
'grant_type': 'refresh_token',
|
'grant_type': 'refresh_token',
|
||||||
@@ -188,7 +238,18 @@ def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
|||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
result = requests.post(TOKEN_URL, data=data, headers=headers)
|
try:
|
||||||
|
result = requests.post(
|
||||||
|
TOKEN_URL,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
except requests.exceptions.Timeout as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('token')
|
||||||
|
) from error
|
||||||
if result.status_code != 200:
|
if result.status_code != 200:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
@@ -201,7 +262,7 @@ def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
|||||||
key='access_token',
|
key='access_token',
|
||||||
value=token_data['access_token'],
|
value=token_data['access_token'],
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True if settings.debug == False else True,
|
secure=True if settings.debug is False else True,
|
||||||
samesite='strict',
|
samesite='strict',
|
||||||
max_age=settings.max_age
|
max_age=settings.max_age
|
||||||
)
|
)
|
||||||
@@ -209,7 +270,7 @@ def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
|||||||
key='refresh_token',
|
key='refresh_token',
|
||||||
value=token_data['refresh_token'] or '',
|
value=token_data['refresh_token'] or '',
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True if settings.debug == False else True,
|
secure=True if settings.debug is False else True,
|
||||||
samesite='strict',
|
samesite='strict',
|
||||||
max_age=30 * 24 * settings.max_age
|
max_age=30 * 24 * settings.max_age
|
||||||
)
|
)
|
||||||
@@ -223,6 +284,7 @@ def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
|||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get('/user/me')
|
@router.get('/user/me')
|
||||||
def me(user: UserPublic = Depends(get_current_user)):
|
def me(user: UserPublic = Depends(get_current_user)):
|
||||||
if not user:
|
if not user:
|
||||||
@@ -233,6 +295,6 @@ def me(user: UserPublic = Depends(get_current_user)):
|
|||||||
'name': user.name,
|
'name': user.name,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'id': user.id,
|
'id': user.id,
|
||||||
'roles': [role.name for role in user.roles]
|
'roles': user.roles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,86 +1,62 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
"""Router for contract resource"""
|
||||||
from fastapi.responses import StreamingResponse
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
from src.contracts.generate_contract import generate_html_contract, generate_recap
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import src.models as models
|
|
||||||
import src.messages as messages
|
|
||||||
import src.contracts.service as service
|
|
||||||
import src.forms.service as form_service
|
|
||||||
import io
|
import io
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
import src.contracts.service as service
|
||||||
|
import src.forms.service as form_service
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.contracts.generate_contract import (generate_html_contract,
|
||||||
|
generate_recap)
|
||||||
|
from src.database import get_session
|
||||||
|
|
||||||
router = APIRouter(prefix='/contracts')
|
router = APIRouter(prefix='/contracts')
|
||||||
|
|
||||||
def compute_recurrent_prices(products_quantities: list[dict], nb_shipment: int):
|
|
||||||
result = 0
|
|
||||||
for product_quantity in products_quantities:
|
|
||||||
product = product_quantity['product']
|
|
||||||
quantity = product_quantity['quantity']
|
|
||||||
result += compute_product_price(product, quantity, nb_shipment)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def compute_occasional_prices(occasionals: list[dict]):
|
|
||||||
result = 0
|
|
||||||
for occasional in occasionals:
|
|
||||||
result += occasional['price']
|
|
||||||
return result
|
|
||||||
|
|
||||||
def compute_product_price(product: models.Product, quantity: int, nb_shipment: int = 1):
|
|
||||||
product_quantity_unit = 1 if product.unit == models.Unit.KILO else 1000
|
|
||||||
final_quantity = quantity if product.price else quantity / product_quantity_unit
|
|
||||||
final_price = product.price if product.price else product.price_kg
|
|
||||||
return final_price * final_quantity * nb_shipment
|
|
||||||
|
|
||||||
def find_dict_in_list(lst, key, value):
|
|
||||||
for i, dic in enumerate(lst):
|
|
||||||
if dic[key].id == value:
|
|
||||||
return i
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def create_occasional_dict(contract_products: list[models.ContractProduct]):
|
|
||||||
result = []
|
|
||||||
for contract_product in contract_products:
|
|
||||||
existing_id = find_dict_in_list(
|
|
||||||
result,
|
|
||||||
'shipment',
|
|
||||||
contract_product.shipment.id
|
|
||||||
)
|
|
||||||
if existing_id < 0:
|
|
||||||
result.append({
|
|
||||||
'shipment': contract_product.shipment,
|
|
||||||
'price': compute_product_price(
|
|
||||||
contract_product.product,
|
|
||||||
contract_product.quantity
|
|
||||||
),
|
|
||||||
'products': [{
|
|
||||||
'product': contract_product.product,
|
|
||||||
'quantity': contract_product.quantity
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
result[existing_id]['products'].append({
|
|
||||||
'product': contract_product.product,
|
|
||||||
'quantity': contract_product.quantity
|
|
||||||
})
|
|
||||||
result[existing_id]['price'] += compute_product_price(
|
|
||||||
contract_product.product,
|
|
||||||
contract_product.quantity
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@router.post('')
|
@router.post('')
|
||||||
async def create_contract(
|
async def create_contract(
|
||||||
contract: models.ContractCreate,
|
contract: models.ContractCreate,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
|
"""Create contract route"""
|
||||||
new_contract = service.create_one(session, contract)
|
new_contract = service.create_one(session, contract)
|
||||||
occasional_contract_products = list(filter(lambda contract_product: contract_product.product.type == models.ProductType.OCCASIONAL, new_contract.products))
|
occasional_contract_products = list(
|
||||||
occasionals = create_occasional_dict(occasional_contract_products)
|
filter(
|
||||||
recurrents = list(map(lambda x: {"product": x.product, "quantity": x.quantity}, filter(lambda contract_product: contract_product.product.type == models.ProductType.RECCURENT, new_contract.products)))
|
lambda contract_product: (
|
||||||
recurrent_price = compute_recurrent_prices(recurrents, len(new_contract.form.shipments))
|
contract_product.product.type == models.ProductType.OCCASIONAL
|
||||||
price = recurrent_price + compute_occasional_prices(occasionals)
|
),
|
||||||
cheques = list(map(lambda x: {"name": x.name, "value": x.value}, new_contract.cheques))
|
new_contract.products
|
||||||
|
)
|
||||||
|
)
|
||||||
|
occasionals = service.create_occasional_dict(occasional_contract_products)
|
||||||
|
recurrents = list(
|
||||||
|
map(
|
||||||
|
lambda x: {'product': x.product, 'quantity': x.quantity},
|
||||||
|
filter(
|
||||||
|
lambda contract_product: (
|
||||||
|
contract_product.product.type ==
|
||||||
|
models.ProductType.RECCURENT
|
||||||
|
),
|
||||||
|
new_contract.products
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
prices = service.generate_products_prices(
|
||||||
|
occasionals,
|
||||||
|
recurrents,
|
||||||
|
new_contract.form.shipments
|
||||||
|
)
|
||||||
|
recurrent_price = prices['recurrent']
|
||||||
|
total_price = prices['total']
|
||||||
|
cheques = list(
|
||||||
|
map(
|
||||||
|
lambda x: {'name': x.name, 'value': x.value},
|
||||||
|
new_contract.cheques
|
||||||
|
)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
pdf_bytes = generate_html_contract(
|
pdf_bytes = generate_html_contract(
|
||||||
new_contract,
|
new_contract,
|
||||||
@@ -88,46 +64,67 @@ async def create_contract(
|
|||||||
occasionals,
|
occasionals,
|
||||||
recurrents,
|
recurrents,
|
||||||
'{:10.2f}'.format(recurrent_price),
|
'{:10.2f}'.format(recurrent_price),
|
||||||
'{:10.2f}'.format(price)
|
'{:10.2f}'.format(total_price)
|
||||||
)
|
)
|
||||||
pdf_file = io.BytesIO(pdf_bytes)
|
pdf_file = io.BytesIO(pdf_bytes)
|
||||||
contract_id = f'{new_contract.firstname}_{new_contract.lastname}_{new_contract.form.productor.type}_{new_contract.form.season}'
|
contract_id = (
|
||||||
service.add_contract_file(session, new_contract.id, pdf_bytes, price)
|
f'{new_contract.firstname}_'
|
||||||
except Exception:
|
f'{new_contract.lastname}_'
|
||||||
raise HTTPException(status_code=400, detail=messages.pdferror)
|
f'{new_contract.form.productor.type}_'
|
||||||
|
f'{new_contract.form.season}'
|
||||||
|
)
|
||||||
|
service.add_contract_file(
|
||||||
|
session, new_contract.id, pdf_bytes, total_price)
|
||||||
|
except Exception as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=messages.pdferror
|
||||||
|
) from error
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
pdf_file,
|
pdf_file,
|
||||||
media_type='application/pdf',
|
media_type='application/pdf',
|
||||||
headers={
|
headers={
|
||||||
'Content-Disposition': f'attachment; filename=contract_{contract_id}.pdf'
|
'Content-Disposition': (
|
||||||
|
f'attachment; filename=contract_{contract_id}.pdf'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{form_id}/base')
|
@router.get('/{form_id}/base')
|
||||||
async def get_base_contract_template(
|
async def get_base_contract_template(
|
||||||
form_id: int,
|
form_id: int,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
|
"""Get contract template route"""
|
||||||
form = form_service.get_one(session, form_id)
|
form = form_service.get_one(session, form_id)
|
||||||
recurrents = list(map(lambda x: {"product": x, "quantity": None}, filter(lambda product: product.type == models.ProductType.RECCURENT, form.productor.products)))
|
recurrents = [
|
||||||
|
{'product': product, 'quantity': None}
|
||||||
|
for product in form.productor.products
|
||||||
|
if product.type == models.ProductType.RECCURENT
|
||||||
|
]
|
||||||
occasionals = [{
|
occasionals = [{
|
||||||
'shipment': sh,
|
'shipment': sh,
|
||||||
'price': None,
|
'price': None,
|
||||||
'products': [{'product': pr, 'quantity': None} for pr in sh.products]
|
'products': [{'product': pr, 'quantity': None} for pr in sh.products]
|
||||||
} for sh in form.shipments]
|
} for sh in form.shipments]
|
||||||
empty_contract = models.ContractPublic(
|
empty_contract = models.ContractPublic(
|
||||||
firstname="",
|
firstname='',
|
||||||
form=form,
|
form=form,
|
||||||
lastname="",
|
lastname='',
|
||||||
email="",
|
email='',
|
||||||
phone="",
|
phone='',
|
||||||
products=[],
|
products=[],
|
||||||
payment_method="cheque",
|
payment_method='cheque',
|
||||||
cheque_quantity=3,
|
cheque_quantity=3,
|
||||||
total_price=0,
|
total_price=0,
|
||||||
id=1
|
id=1
|
||||||
)
|
)
|
||||||
cheques = [{"name": None, "value": None}, {"name": None, "value": None}, {"name": None, "value": None}]
|
cheques = [
|
||||||
|
{'name': None, 'value': None},
|
||||||
|
{'name': None, 'value': None},
|
||||||
|
{'name': None, 'value': None}
|
||||||
|
]
|
||||||
try:
|
try:
|
||||||
pdf_bytes = generate_html_contract(
|
pdf_bytes = generate_html_contract(
|
||||||
empty_contract,
|
empty_contract,
|
||||||
@@ -136,38 +133,60 @@ async def get_base_contract_template(
|
|||||||
recurrents,
|
recurrents,
|
||||||
)
|
)
|
||||||
pdf_file = io.BytesIO(pdf_bytes)
|
pdf_file = io.BytesIO(pdf_bytes)
|
||||||
contract_id = f'{empty_contract.form.productor.type}_{empty_contract.form.season}'
|
contract_id = (
|
||||||
except Exception as e:
|
f'{empty_contract.form.productor.type}_'
|
||||||
print(e)
|
f'{empty_contract.form.season}'
|
||||||
raise HTTPException(status_code=400, detail=messages.pdferror)
|
)
|
||||||
|
except Exception as error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=messages.pdferror
|
||||||
|
) from error
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
pdf_file,
|
pdf_file,
|
||||||
media_type='application/pdf',
|
media_type='application/pdf',
|
||||||
headers={
|
headers={
|
||||||
'Content-Disposition': f'attachment; filename=contract_{contract_id}.pdf'
|
'Content-Disposition': (
|
||||||
|
f'attachment; filename=contract_{contract_id}.pdf'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.ContractPublic])
|
@router.get('', response_model=list[models.ContractPublic])
|
||||||
def get_contracts(
|
def get_contracts(
|
||||||
forms: list[str] = Query([]),
|
forms: list[str] = Query([]),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user)
|
user: models.User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
|
"""Get all contracts route"""
|
||||||
return service.get_all(session, user, forms)
|
return service.get_all(session, user, forms)
|
||||||
|
|
||||||
@router.get('/{id}/file')
|
|
||||||
|
@router.get('/{_id}/file')
|
||||||
def get_contract_file(
|
def get_contract_file(
|
||||||
id: int,
|
_id: int,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user)
|
user: models.User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
if not service.is_allowed(session, user, id):
|
"""Get a contract file (in pdf) route"""
|
||||||
raise HTTPException(status_code=403, detail=messages.Messages.not_allowed('contract', 'get'))
|
if not service.is_allowed(session, user, _id):
|
||||||
contract = service.get_one(session, id)
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contract', 'get')
|
||||||
|
)
|
||||||
|
contract = service.get_one(session, _id)
|
||||||
if contract is None:
|
if contract is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('contract'))
|
raise HTTPException(
|
||||||
filename = f'{contract.form.name.replace(' ', '_')}_{contract.form.season}_{contract.firstname}-{contract.lastname}'
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('contract')
|
||||||
|
)
|
||||||
|
filename = (
|
||||||
|
f'{contract.form.name.replace(' ', '_')}_'
|
||||||
|
f'{contract.form.season}_'
|
||||||
|
f'{contract.firstname}_'
|
||||||
|
f'{contract.lastname}'
|
||||||
|
)
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(contract.file),
|
io.BytesIO(contract.file),
|
||||||
media_type='application/pdf',
|
media_type='application/pdf',
|
||||||
@@ -176,23 +195,55 @@ def get_contract_file(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
'/{_id}/preview-delete',
|
||||||
|
response_model=list[models.DeleteDependency]
|
||||||
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contract', 'delete')
|
||||||
|
)
|
||||||
|
result = []
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{form_id}/files')
|
@router.get('/{form_id}/files')
|
||||||
def get_contract_files(
|
def get_contract_files(
|
||||||
form_id: int,
|
form_id: int,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user)
|
user: models.User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
|
"""Get all contract files for a given form"""
|
||||||
if not form_service.is_allowed(session, user, form_id):
|
if not form_service.is_allowed(session, user, form_id):
|
||||||
raise HTTPException(status_code=403, detail=messages.Messages.not_allowed('contracts', 'get'))
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contracts', 'get')
|
||||||
|
)
|
||||||
form = form_service.get_one(session, form_id=form_id)
|
form = form_service.get_one(session, form_id=form_id)
|
||||||
contracts = service.get_all(session, user, forms=[form.name])
|
contracts = service.get_all(session, user, forms=[form.name])
|
||||||
zipped_contracts = io.BytesIO()
|
zipped_contracts = io.BytesIO()
|
||||||
with zipfile.ZipFile(zipped_contracts, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
|
with zipfile.ZipFile(
|
||||||
|
zipped_contracts,
|
||||||
|
'a',
|
||||||
|
zipfile.ZIP_DEFLATED,
|
||||||
|
False
|
||||||
|
) as zip_file:
|
||||||
for contract in contracts:
|
for contract in contracts:
|
||||||
contract_filename = f'{contract.form.name.replace(' ', '_')}_{contract.form.season}_{contract.firstname}-{contract.lastname}.pdf'
|
contract_filename = (
|
||||||
|
f'{contract.form.name.replace(' ', '_')}_'
|
||||||
|
f'{contract.form.season}_'
|
||||||
|
f'{contract.firstname}_'
|
||||||
|
f'{contract.lastname}'
|
||||||
|
)
|
||||||
zip_file.writestr(contract_filename, contract.file)
|
zip_file.writestr(contract_filename, contract.file)
|
||||||
|
filename = f'{form.name.replace(' ', '_')}_{form.season}'
|
||||||
filename = f'{form.name.replace(" ", "_")}_{form.season}'
|
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(zipped_contracts.getvalue()),
|
io.BytesIO(zipped_contracts.getvalue()),
|
||||||
media_type='application/zip',
|
media_type='application/zip',
|
||||||
@@ -201,39 +252,75 @@ def get_contract_files(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{form_id}/recap')
|
@router.get('/{form_id}/recap')
|
||||||
def get_contract_recap(
|
def get_contract_recap(
|
||||||
form_id: int,
|
form_id: int,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user)
|
user: models.User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
|
"""Get a contract recap for a given form"""
|
||||||
if not form_service.is_allowed(session, user, form_id):
|
if not form_service.is_allowed(session, user, form_id):
|
||||||
raise HTTPException(status_code=403, detail=messages.Messages.not_allowed('contract recap', 'get'))
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contract recap', 'get')
|
||||||
|
)
|
||||||
form = form_service.get_one(session, form_id=form_id)
|
form = form_service.get_one(session, form_id=form_id)
|
||||||
contracts = service.get_all(session, user, forms=[form.name])
|
contracts = service.get_all(session, user, forms=[form.name])
|
||||||
|
filename = f'{form.name}_recapitulatif_contrats.ods'
|
||||||
|
recap = generate_recap(contracts, form)
|
||||||
|
if recap is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404, detail=messages.Messages.not_found('contracts')
|
||||||
|
)
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(generate_recap(contracts, form)),
|
io.BytesIO(recap),
|
||||||
media_type='application/zip',
|
media_type='application/vnd.oasis.opendocument.spreadsheet',
|
||||||
headers={
|
headers={
|
||||||
'Content-Disposition': f'attachment; filename=filename.ods'
|
'Content-Disposition': (
|
||||||
|
f'attachment; filename={filename}'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ContractPublic)
|
|
||||||
def get_contract(id: int, session: Session = Depends(get_session), user: models.User = Depends(get_current_user)):
|
@router.get('/{_id}', response_model=models.ContractPublic)
|
||||||
if not service.is_allowed(session, user, id):
|
def get_contract(
|
||||||
raise HTTPException(status_code=403, detail=messages.Messages.not_allowed('contract', 'get'))
|
_id: int,
|
||||||
result = service.get_one(session, id)
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""Get a contract route"""
|
||||||
|
if not service.is_allowed(session, user, _id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contract', 'get')
|
||||||
|
)
|
||||||
|
result = service.get_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('contract'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('contract')
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.ContractPublic)
|
|
||||||
def delete_contract(id: int, session: Session = Depends(get_session), user: models.User = Depends(get_current_user)):
|
@router.delete('/{_id}', response_model=models.ContractPublic)
|
||||||
if not service.is_allowed(session, user, id):
|
def delete_contract(
|
||||||
raise HTTPException(status_code=403, detail=messages.Messages.not_allowed('contract', 'delete'))
|
_id: int,
|
||||||
result = service.delete_one(session, id)
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""Delete contract route"""
|
||||||
|
if not service.is_allowed(session, user, _id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('contract', 'delete')
|
||||||
|
)
|
||||||
|
result = service.delete_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('contract'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('contract')
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
|
||||||
|
import html
|
||||||
|
import io
|
||||||
|
import pathlib
|
||||||
|
import string
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
import src.models as models
|
import odfdo
|
||||||
import html
|
from src import models
|
||||||
|
from src.contracts import service
|
||||||
from weasyprint import HTML
|
from weasyprint import HTML
|
||||||
import io
|
|
||||||
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
def generate_html_contract(
|
def generate_html_contract(
|
||||||
contract: models.Contract,
|
contract: models.Contract,
|
||||||
@@ -14,10 +18,24 @@ def generate_html_contract(
|
|||||||
reccurents: list[dict],
|
reccurents: list[dict],
|
||||||
recurrent_price: float | None = None,
|
recurrent_price: float | None = None,
|
||||||
total_price: float | None = None
|
total_price: float | None = None
|
||||||
):
|
) -> bytes:
|
||||||
|
"""Generate a html contract
|
||||||
|
Arguments:
|
||||||
|
contract(models.Contract): Contract source.
|
||||||
|
cheques(list[dict]): cheques formated in dict.
|
||||||
|
occasionals(list[dict]): occasional products.
|
||||||
|
reccurents(list[dict]): recurrent products.
|
||||||
|
recurrent_price(float | None = None): total price of recurent products.
|
||||||
|
total_price(float | None = Non): total price.
|
||||||
|
Return:
|
||||||
|
result(bytes): contract file in pdf as bytes.
|
||||||
|
"""
|
||||||
template_dir = pathlib.Path("./src/contracts/templates").resolve()
|
template_dir = pathlib.Path("./src/contracts/templates").resolve()
|
||||||
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
|
template_loader = jinja2.FileSystemLoader(searchpath=template_dir)
|
||||||
template_env = jinja2.Environment(loader=template_loader, autoescape=jinja2.select_autoescape(["html", "xml"]))
|
template_env = jinja2.Environment(
|
||||||
|
loader=template_loader,
|
||||||
|
autoescape=jinja2.select_autoescape(["html", "xml"])
|
||||||
|
)
|
||||||
template_file = "layout.html"
|
template_file = "layout.html"
|
||||||
template = template_env.get_template(template_file)
|
template = template_env.get_template(template_file)
|
||||||
output_text = template.render(
|
output_text = template.render(
|
||||||
@@ -28,57 +46,544 @@ def generate_html_contract(
|
|||||||
referer_email=contract.form.referer.email,
|
referer_email=contract.form.referer.email,
|
||||||
productor_name=contract.form.productor.name,
|
productor_name=contract.form.productor.name,
|
||||||
productor_address=contract.form.productor.address,
|
productor_address=contract.form.productor.address,
|
||||||
payment_methods_map={"cheque": "Ordre du chèque", "transfer": "virements"},
|
payment_methods_map={
|
||||||
|
"cheque": "Ordre du chèque",
|
||||||
|
"transfer": "virements"},
|
||||||
productor_payment_methods=contract.form.productor.payment_methods,
|
productor_payment_methods=contract.form.productor.payment_methods,
|
||||||
member_name=f'{html.escape(contract.firstname)} {html.escape(contract.lastname)}',
|
member_name=f'{
|
||||||
member_email=html.escape(contract.email),
|
html.escape(
|
||||||
member_phone=html.escape(contract.phone),
|
contract.firstname)} {
|
||||||
|
html.escape(
|
||||||
|
contract.lastname)}',
|
||||||
|
member_email=html.escape(
|
||||||
|
contract.email),
|
||||||
|
member_phone=html.escape(
|
||||||
|
contract.phone),
|
||||||
contract_start_date=contract.form.start,
|
contract_start_date=contract.form.start,
|
||||||
contract_end_date=contract.form.end,
|
contract_end_date=contract.form.end,
|
||||||
occasionals=occasionals,
|
occasionals=occasionals,
|
||||||
recurrents=reccurents,
|
recurrents=reccurents,
|
||||||
recurrent_price=recurrent_price,
|
recurrent_price=recurrent_price,
|
||||||
total_price=total_price,
|
total_price=total_price,
|
||||||
contract_payment_method={"cheque": "chèque", "transfer": "virements"}[contract.payment_method],
|
contract_payment_method={
|
||||||
cheques=cheques
|
"cheque": "chèque",
|
||||||
)
|
"transfer": "virements"}[
|
||||||
# options = {
|
contract.payment_method],
|
||||||
# 'page-size': 'Letter',
|
cheques=cheques)
|
||||||
# 'margin-top': '0.5in',
|
|
||||||
# 'margin-right': '0.5in',
|
|
||||||
# 'margin-bottom': '0.5in',
|
|
||||||
# 'margin-left': '0.5in',
|
|
||||||
# 'encoding': "UTF-8",
|
|
||||||
# 'print-media-type': True,
|
|
||||||
# "disable-javascript": True,
|
|
||||||
# "disable-external-links": True,
|
|
||||||
# 'enable-local-file-access': False,
|
|
||||||
# "disable-local-file-access": True,
|
|
||||||
# "no-images": True,
|
|
||||||
# }
|
|
||||||
|
|
||||||
return HTML(
|
return HTML(
|
||||||
string=output_text,
|
string=output_text,
|
||||||
base_url=template_dir,
|
base_url=template_dir,
|
||||||
).write_pdf()
|
).write_pdf()
|
||||||
|
|
||||||
from odfdo import Document, Table, Row, Cell
|
|
||||||
|
def flatten(xss):
|
||||||
|
"""flatten a list of list.
|
||||||
|
"""
|
||||||
|
return [x for xs in xss for x in xs]
|
||||||
|
|
||||||
|
|
||||||
|
def create_column_style_width(size: str) -> odfdo.Style:
|
||||||
|
"""Create a table columm style for a given width.
|
||||||
|
Paramenters:
|
||||||
|
size(str): size of the style (format <number><unit>)
|
||||||
|
unit can be in, cm... see odfdo documentation.
|
||||||
|
Returns:
|
||||||
|
odfdo.Style with the correct column-width attribute.
|
||||||
|
"""
|
||||||
|
return odfdo.Element.from_tag(
|
||||||
|
'<style:style style:name="product-table.A" style:family="table-column">'
|
||||||
|
f'<style:table-column-properties style:column-width="{size}"/>'
|
||||||
|
'</style:style>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_row_style_height(size: str) -> odfdo.Style:
|
||||||
|
"""Create a table height style for a given height.
|
||||||
|
Paramenters:
|
||||||
|
size(str): size of the style (format <number><unit>)
|
||||||
|
unit can be in, cm... see odfdo documentation.
|
||||||
|
Returns:
|
||||||
|
odfdo.Style with the correct column-height attribute.
|
||||||
|
"""
|
||||||
|
return odfdo.Element.from_tag(
|
||||||
|
'<style:style style:name="product-table.A" style:family="table-row">'
|
||||||
|
f'<style:table-row-properties style:row-height="{size}"/>'
|
||||||
|
'</style:style>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_currency_style(name: str = 'currency-euro'):
|
||||||
|
"""Create a table currency style.
|
||||||
|
Paramenters:
|
||||||
|
name(str): name of the style (default to `currency-euro`).
|
||||||
|
Returns:
|
||||||
|
odfdo.Style with the correct column-height attribute.
|
||||||
|
"""
|
||||||
|
return odfdo.Element.from_tag(
|
||||||
|
f"""
|
||||||
|
<number:currency-style style:name="{name}">
|
||||||
|
<number:number number:min-integer-digits="1"
|
||||||
|
number:decimal-places="2"/>
|
||||||
|
<number:text> €</number:text>
|
||||||
|
</number:currency-style>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_cell_style(
|
||||||
|
name: str = "centered-cell",
|
||||||
|
font_size: str = '10pt',
|
||||||
|
bold: bool = False,
|
||||||
|
background_color: str = '#FFFFFF',
|
||||||
|
color: str = '#000000',
|
||||||
|
currency: bool = False,
|
||||||
|
) -> odfdo.Style:
|
||||||
|
"""Create a cell style
|
||||||
|
Paramenters:
|
||||||
|
name(str): name of the style (default to `centered-cell`).
|
||||||
|
font_size(str): font_size of the cell (default to `10pt`).
|
||||||
|
bold(str): is the text bold (default to `False`).
|
||||||
|
background_color(str): background_color of the cell
|
||||||
|
(default to `#FFFFFF`).
|
||||||
|
color(str): color of the text of the cell (default to `#000000`).
|
||||||
|
currency(str): is the cell a currency (default to `False`).
|
||||||
|
Returns:
|
||||||
|
odfdo.Style with the correct column-height attribute.
|
||||||
|
"""
|
||||||
|
bold_attr = """
|
||||||
|
fo:font-weight="bold"
|
||||||
|
style:font-weight-asian="bold"
|
||||||
|
style:font-weight-complex="bold"
|
||||||
|
""" if bold else ''
|
||||||
|
currency_attr = """
|
||||||
|
style:data-style-name="currency-euro">
|
||||||
|
""" if currency else ''
|
||||||
|
return odfdo.Element.from_tag(
|
||||||
|
f"""<style:style style:name="{name}" style:family="table-cell"
|
||||||
|
{currency_attr}>
|
||||||
|
<style:table-cell-properties
|
||||||
|
fo:border="0.75pt solid #000000"
|
||||||
|
style:vertical-align="middle"
|
||||||
|
fo:wrap-option="wrap"
|
||||||
|
fo:background-color="{background_color}"/>
|
||||||
|
<style:paragraph-properties fo:text-align="center"/>
|
||||||
|
<style:text-properties
|
||||||
|
{bold_attr}
|
||||||
|
fo:font-size="{font_size}"
|
||||||
|
fo:color="{color}"/>
|
||||||
|
</style:style>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_cell_style(
|
||||||
|
document: odfdo.Document,
|
||||||
|
table: odfdo.Table,
|
||||||
|
currency_cols: list[int]
|
||||||
|
):
|
||||||
|
"""Apply cell style
|
||||||
|
"""
|
||||||
|
document.insert_style(
|
||||||
|
style=create_currency_style(),
|
||||||
|
)
|
||||||
|
header_style = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="header-cells",
|
||||||
|
bold=True,
|
||||||
|
font_size='12pt',
|
||||||
|
background_color="#3480eb",
|
||||||
|
color="#FFF"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body_style_even = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="body-style-even",
|
||||||
|
bold=False,
|
||||||
|
background_color="#e8eaed",
|
||||||
|
color="#000000",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body_style_odd = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="body-style-odd",
|
||||||
|
bold=False,
|
||||||
|
background_color="#FFFFFF",
|
||||||
|
color="#000000",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
footer_style = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="footer-cells",
|
||||||
|
bold=True,
|
||||||
|
font_size='12pt',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body_style_even_currency = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="body-style-even-currency",
|
||||||
|
bold=False,
|
||||||
|
background_color="#e8eaed",
|
||||||
|
color="#000000",
|
||||||
|
currency=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body_style_odd_currency = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="body-style-odd-currency",
|
||||||
|
bold=False,
|
||||||
|
background_color="#FFFFFF",
|
||||||
|
color="#000000",
|
||||||
|
currency=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
footer_style_currency = document.insert_style(
|
||||||
|
create_cell_style(
|
||||||
|
name="footer-cells-currency",
|
||||||
|
bold=True,
|
||||||
|
font_size='12pt',
|
||||||
|
currency=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for index, row in enumerate(table.get_rows()):
|
||||||
|
style = body_style_even
|
||||||
|
currency_style = body_style_even_currency
|
||||||
|
if index == 0 or index == 1:
|
||||||
|
style = header_style
|
||||||
|
elif index == len(table.get_rows()) - 1:
|
||||||
|
style = footer_style
|
||||||
|
currency_style = footer_style_currency
|
||||||
|
elif index % 2 == 0:
|
||||||
|
style = body_style_even
|
||||||
|
currency_style = body_style_even_currency
|
||||||
|
else:
|
||||||
|
style = body_style_odd
|
||||||
|
currency_style = body_style_odd_currency
|
||||||
|
for cell_index, cell in enumerate(row.get_cells()):
|
||||||
|
if cell_index in currency_cols and not (index == 0 or index == 1):
|
||||||
|
cell.style = currency_style
|
||||||
|
else:
|
||||||
|
cell.style = style
|
||||||
|
|
||||||
|
|
||||||
|
def apply_column_height_style(
|
||||||
|
document: odfdo.Document,
|
||||||
|
table: odfdo.Table
|
||||||
|
):
|
||||||
|
"""Apply column height for a given table
|
||||||
|
"""
|
||||||
|
header_style = document.insert_style(
|
||||||
|
style=create_row_style_height('1.60cm'), name='1.60cm', automatic=True
|
||||||
|
)
|
||||||
|
body_style = document.insert_style(
|
||||||
|
style=create_row_style_height('0.90cm'), name='0.90cm', automatic=True
|
||||||
|
)
|
||||||
|
for index, row in enumerate(table.get_rows()):
|
||||||
|
if index == 1:
|
||||||
|
row.style = header_style
|
||||||
|
else:
|
||||||
|
row.style = body_style
|
||||||
|
|
||||||
|
|
||||||
|
def apply_cell_style_by_column(
|
||||||
|
table: odfdo.Table,
|
||||||
|
style: odfdo.Style,
|
||||||
|
col_index: int
|
||||||
|
):
|
||||||
|
"""Apply cell style for a given table
|
||||||
|
"""
|
||||||
|
for cell in table.get_column_cells(col_index):
|
||||||
|
cell.style = style
|
||||||
|
|
||||||
|
|
||||||
|
def apply_column_width_style(
|
||||||
|
document: odfdo.Document,
|
||||||
|
table: odfdo.Table,
|
||||||
|
widths: list[str]
|
||||||
|
):
|
||||||
|
"""Apply column width style to a table.
|
||||||
|
Parameters:
|
||||||
|
document(odfdo.Document): Document where the table is located.
|
||||||
|
table(odfdo.Table): Table to apply columns widths.
|
||||||
|
widths(list[str]): list of width in format <number><unit> unit ca be
|
||||||
|
in, cm... see odfdo documentation.
|
||||||
|
"""
|
||||||
|
styles = []
|
||||||
|
for w in widths:
|
||||||
|
styles.append(document.insert_style(
|
||||||
|
style=create_column_style_width(w), name=w, automatic=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
for position in range(table.width):
|
||||||
|
col = table.get_column(position)
|
||||||
|
col.style = styles[position]
|
||||||
|
table.set_column(position, col)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ods_letters(n: int):
|
||||||
|
"""Generate letters following excel format.
|
||||||
|
Arguments:
|
||||||
|
n(int): `n` letters to generate.
|
||||||
|
Return:
|
||||||
|
result(list[str]): list of `n` letters that follow excel pattern.
|
||||||
|
"""
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
result = []
|
||||||
|
for i in range(n):
|
||||||
|
if i > len(letters) - 1:
|
||||||
|
letter = f'{letters[int(i / len(letters)) - 1]}'
|
||||||
|
letter += f'{letters[i % len(letters)]}'
|
||||||
|
result.append(letter)
|
||||||
|
continue
|
||||||
|
letter = letters[i]
|
||||||
|
result.append(letters[i])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def compute_contract_prices(contract: models.Contract) -> dict:
|
||||||
|
"""Compute price for a give contract.
|
||||||
|
"""
|
||||||
|
occasional_contract_products = list(
|
||||||
|
filter(
|
||||||
|
lambda contract_product: (
|
||||||
|
contract_product.product.type == models.ProductType.OCCASIONAL
|
||||||
|
),
|
||||||
|
contract.products
|
||||||
|
)
|
||||||
|
)
|
||||||
|
occasionals_dict = service.create_occasional_dict(
|
||||||
|
occasional_contract_products)
|
||||||
|
recurrents_dict = list(
|
||||||
|
map(
|
||||||
|
lambda x: {'product': x.product, 'quantity': x.quantity},
|
||||||
|
filter(
|
||||||
|
lambda contract_product: (
|
||||||
|
contract_product.product.type ==
|
||||||
|
models.ProductType.RECCURENT
|
||||||
|
),
|
||||||
|
contract.products
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
prices = service.generate_products_prices(
|
||||||
|
occasionals_dict,
|
||||||
|
recurrents_dict,
|
||||||
|
contract.form.shipments
|
||||||
|
)
|
||||||
|
return prices
|
||||||
|
|
||||||
|
|
||||||
|
def transform_formula_cells(sheet: odfdo.Spreadsheet):
|
||||||
|
"""Transform cell value to a formula using odfdo.
|
||||||
|
"""
|
||||||
|
for row in sheet.get_rows():
|
||||||
|
for cell in row.get_cells():
|
||||||
|
if not cell.value or cell.get_attribute("office:value-type") == "float":
|
||||||
|
continue
|
||||||
|
if '=' in cell.value:
|
||||||
|
formula = cell.value
|
||||||
|
cell.clear()
|
||||||
|
cell.formula = formula
|
||||||
|
|
||||||
|
|
||||||
|
def merge_shipment_cells(
|
||||||
|
sheet: odfdo.Spreadsheet,
|
||||||
|
prefix_header: list[str],
|
||||||
|
recurrents: list[str],
|
||||||
|
occasionnals: list[str],
|
||||||
|
shipments: list[models.Shipment]
|
||||||
|
):
|
||||||
|
"""Merge cells for shipment header.
|
||||||
|
"""
|
||||||
|
index = len(prefix_header) + len(recurrents) + 1
|
||||||
|
for _ in enumerate(shipments):
|
||||||
|
startcol = index
|
||||||
|
endcol = index+len(occasionnals) - 1
|
||||||
|
sheet.set_span((startcol, 0, endcol, 0), merge=True)
|
||||||
|
index += len(occasionnals)
|
||||||
|
|
||||||
|
|
||||||
def generate_recap(
|
def generate_recap(
|
||||||
contracts: list[models.Contract],
|
contracts: list[models.Contract],
|
||||||
form: models.Form,
|
form: models.Form,
|
||||||
):
|
):
|
||||||
data = [
|
"""Generate excel recap for a list of contracts.
|
||||||
["nom", "email"],
|
"""
|
||||||
|
product_unit_map = {
|
||||||
|
'1': 'g',
|
||||||
|
'2': 'Kg',
|
||||||
|
'3': 'Piece'
|
||||||
|
}
|
||||||
|
if len(contracts) <= 0:
|
||||||
|
# TODO: raise correct exception
|
||||||
|
return None
|
||||||
|
first_contract = contracts[0]
|
||||||
|
reccurents_sorted = sorted(
|
||||||
|
[
|
||||||
|
product for product in first_contract.products
|
||||||
|
if product.product.type == models.ProductType.RECCURENT
|
||||||
|
],
|
||||||
|
key=lambda x: (x.product.name, x.product.quantity)
|
||||||
|
)
|
||||||
|
recurrents = [
|
||||||
|
f'{pr.product.name}{f' - {pr.product.quantity}{pr.product.quantity_unit}'
|
||||||
|
if pr.product.quantity else ''} ({product_unit_map[pr.product.unit]})'
|
||||||
|
for pr in reccurents_sorted
|
||||||
]
|
]
|
||||||
doc = Document("spreadsheet")
|
occasionnals_sorted = sorted(
|
||||||
sheet = Table(name="Recap")
|
[
|
||||||
|
product for product in first_contract.products
|
||||||
|
if product.product.type == models.ProductType.OCCASIONAL
|
||||||
|
],
|
||||||
|
key=lambda x: (x.shipment.name, x.product.name)
|
||||||
|
)
|
||||||
|
occasionnals = [
|
||||||
|
f'{pr.product.name}{f' - {pr.product.quantity}{pr.product.quantity_unit}'
|
||||||
|
if pr.product.quantity else ''} ({product_unit_map[pr.product.unit]})'
|
||||||
|
for pr in occasionnals_sorted
|
||||||
|
]
|
||||||
|
shipments = form.shipments
|
||||||
|
occasionnals_header = [
|
||||||
|
occ for shipment in shipments for occ in occasionnals
|
||||||
|
]
|
||||||
|
|
||||||
|
info_header: list[str] = ['', 'Nom', 'Email']
|
||||||
|
cheque_header: list[str] = ['Cheque 1', 'Cheque 2', 'Cheque 3']
|
||||||
|
payment_header = (
|
||||||
|
cheque_header +
|
||||||
|
[f'Total {len(shipments)} livraisons + produits occasionnels']
|
||||||
|
)
|
||||||
|
prefix_header: list[str] = (
|
||||||
|
info_header +
|
||||||
|
payment_header
|
||||||
|
)
|
||||||
|
suffix_header: list[str] = [
|
||||||
|
'Total produits occasionnels',
|
||||||
|
'Remarques',
|
||||||
|
'Nom'
|
||||||
|
]
|
||||||
|
|
||||||
|
shipment_header = flatten([
|
||||||
|
[f'{shipment.name} - {shipment.date.strftime('%Y-%m-%d')}'] +
|
||||||
|
['' * len(occasionnals)] for shipment in shipments] +
|
||||||
|
[''] * len(suffix_header)
|
||||||
|
)
|
||||||
|
|
||||||
|
header: list[str] = (
|
||||||
|
prefix_header +
|
||||||
|
recurrents +
|
||||||
|
['Total produits récurrents'] +
|
||||||
|
occasionnals_header +
|
||||||
|
suffix_header
|
||||||
|
)
|
||||||
|
|
||||||
|
letters = generate_ods_letters(len(header))
|
||||||
|
payment_formula_letters = letters[
|
||||||
|
len(info_header):len(info_header) + len(payment_header)
|
||||||
|
]
|
||||||
|
recurent_formula_letters = letters[
|
||||||
|
len(info_header)+len(payment_formula_letters):
|
||||||
|
len(info_header)+len(payment_formula_letters)+len(recurrents) + 1
|
||||||
|
]
|
||||||
|
occasionnals_formula_letters = letters[
|
||||||
|
len(info_header)+len(payment_formula_letters) +
|
||||||
|
len(recurent_formula_letters):
|
||||||
|
len(info_header)+len(payment_formula_letters) +
|
||||||
|
len(recurent_formula_letters)+len(occasionnals_header) + 1
|
||||||
|
]
|
||||||
|
|
||||||
|
footer = (
|
||||||
|
['', 'Total contrats', ''] +
|
||||||
|
[f'=SUM({letter}3:{letter}{2+len(contracts)})'
|
||||||
|
for letter in payment_formula_letters] +
|
||||||
|
[f'=SUM({letter}3:{letter}{2+len(contracts)})'
|
||||||
|
for letter in recurent_formula_letters] +
|
||||||
|
[f'=SUM({letter}3:{letter}{2+len(contracts)})'
|
||||||
|
for letter in occasionnals_formula_letters]
|
||||||
|
)
|
||||||
|
|
||||||
|
main_data = []
|
||||||
|
for index, contract in enumerate(contracts):
|
||||||
|
prices = compute_contract_prices(contract)
|
||||||
|
occasionnal_sorted = sorted(
|
||||||
|
[
|
||||||
|
product for product in contract.products
|
||||||
|
if product.product.type == models.ProductType.OCCASIONAL
|
||||||
|
],
|
||||||
|
key=lambda x: (x.shipment.name, x.product.name)
|
||||||
|
)
|
||||||
|
recurrent_sorted = sorted(
|
||||||
|
[
|
||||||
|
product for product in contract.products
|
||||||
|
if product.product.type == models.ProductType.RECCURENT
|
||||||
|
],
|
||||||
|
key=lambda x: (x.product.name, x.product.quantity)
|
||||||
|
)
|
||||||
|
|
||||||
|
main_data.append([
|
||||||
|
f'{index + 1}',
|
||||||
|
f'{contract.firstname} {contract.lastname}',
|
||||||
|
f'{contract.email}',
|
||||||
|
*[float(contract.cheques[i].value)
|
||||||
|
if len(contract.cheques) > i
|
||||||
|
else ''
|
||||||
|
for i in range(3)],
|
||||||
|
prices['total'],
|
||||||
|
*[pr.quantity for pr in recurrent_sorted],
|
||||||
|
prices['recurrent'],
|
||||||
|
*[pr.quantity for pr in occasionnal_sorted],
|
||||||
|
prices['occasionnal'],
|
||||||
|
'',
|
||||||
|
f'{contract.firstname} {contract.lastname}',
|
||||||
|
])
|
||||||
|
|
||||||
|
data = [
|
||||||
|
[''] * (len(prefix_header) + len(recurrents) + 1) + shipment_header,
|
||||||
|
header,
|
||||||
|
*main_data,
|
||||||
|
footer
|
||||||
|
]
|
||||||
|
|
||||||
|
doc = odfdo.Document('spreadsheet')
|
||||||
|
sheet = doc.body.get_sheet(0)
|
||||||
|
sheet.name = 'Recap'
|
||||||
sheet.set_values(data)
|
sheet.set_values(data)
|
||||||
|
|
||||||
doc.body.append(sheet)
|
if len(occasionnals) > 0:
|
||||||
|
merge_shipment_cells(
|
||||||
|
sheet,
|
||||||
|
prefix_header,
|
||||||
|
recurrents,
|
||||||
|
occasionnals,
|
||||||
|
shipments
|
||||||
|
)
|
||||||
|
|
||||||
|
transform_formula_cells(sheet)
|
||||||
|
|
||||||
|
apply_column_width_style(
|
||||||
|
doc,
|
||||||
|
doc.body.get_table(0),
|
||||||
|
['2cm'] +
|
||||||
|
['6cm'] * 2 +
|
||||||
|
['2.40cm'] * (len(payment_header) - 1) +
|
||||||
|
['4cm'] * len(recurrents) +
|
||||||
|
['4cm'] +
|
||||||
|
['4cm'] * (len(occasionnals_header) + 1) +
|
||||||
|
['4cm', '8cm', '6cm']
|
||||||
|
)
|
||||||
|
apply_column_height_style(
|
||||||
|
doc,
|
||||||
|
doc.body.get_table(0),
|
||||||
|
)
|
||||||
|
apply_cell_style(
|
||||||
|
doc,
|
||||||
|
doc.body.get_table(0),
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
doc.body.append(sheet)
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
doc.save(buffer)
|
doc.save(buffer)
|
||||||
|
|
||||||
return buffer.getvalue()
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,57 @@
|
|||||||
|
"""Contract service responsible for read, create, update and delete contracts"""
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
user: models.User,
|
user: models.User,
|
||||||
forms: list[str] = [],
|
forms: list[str] | None = None,
|
||||||
form_id: int | None = None,
|
form_id: int | None = None,
|
||||||
) -> list[models.ContractPublic]:
|
) -> list[models.ContractPublic]:
|
||||||
statement = select(models.Contract)\
|
"""Get all contracts"""
|
||||||
.join(models.Form, models.Contract.form_id == models.Form.id)\
|
statement = (
|
||||||
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
|
select(models.Contract)
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
.join(
|
||||||
|
models.Form,
|
||||||
|
models.Contract.form_id == models.Form.id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Form.productor_id == models.Productor.id
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
models.Productor.type.in_(
|
||||||
|
[r.name for r in user.roles]
|
||||||
|
)
|
||||||
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
if len(forms) > 0:
|
)
|
||||||
|
if forms:
|
||||||
statement = statement.where(models.Form.name.in_(forms))
|
statement = statement.where(models.Form.name.in_(forms))
|
||||||
if form_id:
|
if form_id:
|
||||||
statement = statement.where(models.Form.id == form_id)
|
statement = statement.where(models.Form.id == form_id)
|
||||||
return session.exec(statement.order_by(models.Contract.id)).all()
|
return session.exec(statement.order_by(models.Contract.id)).all()
|
||||||
|
|
||||||
def get_one(session: Session, contract_id: int) -> models.ContractPublic:
|
|
||||||
|
def get_one(
|
||||||
|
session: Session,
|
||||||
|
contract_id: int
|
||||||
|
) -> models.ContractPublic:
|
||||||
|
"""Get one contract"""
|
||||||
return session.get(models.Contract, contract_id)
|
return session.get(models.Contract, contract_id)
|
||||||
|
|
||||||
def create_one(session: Session, contract: models.ContractCreate) -> models.ContractPublic:
|
|
||||||
contract_create = contract.model_dump(exclude_unset=True, exclude=["products", "cheques"])
|
def create_one(
|
||||||
|
session: Session,
|
||||||
|
contract: models.ContractCreate
|
||||||
|
) -> models.ContractPublic:
|
||||||
|
"""Create one contract"""
|
||||||
|
contract_create = contract.model_dump(
|
||||||
|
exclude_unset=True,
|
||||||
|
exclude=["products", "cheques"]
|
||||||
|
)
|
||||||
new_contract = models.Contract(**contract_create)
|
new_contract = models.Contract(**contract_create)
|
||||||
|
|
||||||
new_contract.cheques = [
|
new_contract.cheques = [
|
||||||
@@ -45,10 +74,27 @@ def create_one(session: Session, contract: models.ContractCreate) -> models.Cont
|
|||||||
session.add(new_contract)
|
session.add(new_contract)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_contract)
|
session.refresh(new_contract)
|
||||||
return new_contract
|
|
||||||
|
|
||||||
def add_contract_file(session: Session, id: int, file: bytes, price: float):
|
statement = (
|
||||||
statement = select(models.Contract).where(models.Contract.id == id)
|
select(models.Contract)
|
||||||
|
.where(models.Contract.id == new_contract.id)
|
||||||
|
.options(
|
||||||
|
selectinload(models.Contract.form)
|
||||||
|
.selectinload(models.Form.productor)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return session.exec(statement).one()
|
||||||
|
|
||||||
|
|
||||||
|
def add_contract_file(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
file: bytes,
|
||||||
|
price: float
|
||||||
|
):
|
||||||
|
"""Add a file to an existing contract"""
|
||||||
|
statement = select(models.Contract).where(models.Contract.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
contract = result.first()
|
contract = result.first()
|
||||||
contract.total_price = price
|
contract.total_price = price
|
||||||
@@ -58,8 +104,14 @@ def add_contract_file(session: Session, id: int, file: bytes, price: float):
|
|||||||
session.refresh(contract)
|
session.refresh(contract)
|
||||||
return contract
|
return contract
|
||||||
|
|
||||||
def update_one(session: Session, id: int, contract: models.ContractUpdate) -> models.ContractPublic:
|
|
||||||
statement = select(models.Contract).where(models.Contract.id == id)
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
contract: models.ContractUpdate
|
||||||
|
) -> models.ContractPublic:
|
||||||
|
"""Update one contract"""
|
||||||
|
statement = select(models.Contract).where(models.Contract.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_contract = result.first()
|
new_contract = result.first()
|
||||||
if not new_contract:
|
if not new_contract:
|
||||||
@@ -72,8 +124,13 @@ def update_one(session: Session, id: int, contract: models.ContractUpdate) -> mo
|
|||||||
session.refresh(new_contract)
|
session.refresh(new_contract)
|
||||||
return new_contract
|
return new_contract
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.ContractPublic:
|
|
||||||
statement = select(models.Contract).where(models.Contract.id == id)
|
def delete_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int
|
||||||
|
) -> models.ContractPublic:
|
||||||
|
"""Delete one contract"""
|
||||||
|
statement = select(models.Contract).where(models.Contract.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
contract = result.first()
|
contract = result.first()
|
||||||
if not contract:
|
if not contract:
|
||||||
@@ -83,11 +140,129 @@ def delete_one(session: Session, id: int) -> models.ContractPublic:
|
|||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def is_allowed(session: Session, user: models.User, id: int) -> bool:
|
|
||||||
statement = select(models.Contract)\
|
def is_allowed(
|
||||||
.join(models.Form, models.Contract.form_id == models.Form.id)\
|
session: Session,
|
||||||
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
|
user: models.User,
|
||||||
.where(models.Contract.id == id)\
|
_id: int
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
) -> bool:
|
||||||
|
"""Determine if a user is allowed to access a contract by id"""
|
||||||
|
statement = (
|
||||||
|
select(models.Contract)
|
||||||
|
.join(
|
||||||
|
models.Form,
|
||||||
|
models.Contract.form_id == models.Form.id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Form.productor_id == models.Productor.id
|
||||||
|
)
|
||||||
|
.where(models.Contract.id == _id)
|
||||||
|
.where(
|
||||||
|
models.Productor.type.in_(
|
||||||
|
[r.name for r in user.roles]
|
||||||
|
)
|
||||||
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
)
|
||||||
return len(session.exec(statement).all()) > 0
|
return len(session.exec(statement).all()) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def compute_recurrent_prices(
|
||||||
|
products_quantities: list[dict],
|
||||||
|
nb_shipment: int
|
||||||
|
):
|
||||||
|
"""Compute price for recurrent products"""
|
||||||
|
result = 0
|
||||||
|
for product_quantity in products_quantities:
|
||||||
|
product = product_quantity['product']
|
||||||
|
quantity = product_quantity['quantity']
|
||||||
|
result += compute_product_price(product, quantity, nb_shipment)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def compute_occasional_prices(occasionals: list[dict]):
|
||||||
|
"""Compute prices for occassional products"""
|
||||||
|
result = 0
|
||||||
|
for occasional in occasionals:
|
||||||
|
result += occasional['price']
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def compute_product_price(
|
||||||
|
product: models.Product,
|
||||||
|
quantity: int,
|
||||||
|
nb_shipment: int = 1
|
||||||
|
):
|
||||||
|
"""Compute price for a product"""
|
||||||
|
product_quantity_unit = (
|
||||||
|
1 if product.unit == models.Unit.KILO else 1000
|
||||||
|
)
|
||||||
|
final_quantity = (
|
||||||
|
quantity if product.price else quantity / product_quantity_unit
|
||||||
|
)
|
||||||
|
final_price = (
|
||||||
|
product.price if product.price else product.price_kg
|
||||||
|
)
|
||||||
|
return final_price * final_quantity * nb_shipment
|
||||||
|
|
||||||
|
|
||||||
|
def find_dict_in_list(lst, key, value):
|
||||||
|
"""Find the index of a dictionnary in a list of dictionnaries given a key
|
||||||
|
and a value.
|
||||||
|
"""
|
||||||
|
for i, dic in enumerate(lst):
|
||||||
|
if dic[key].id == value:
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def create_occasional_dict(contract_products: list[models.ContractProduct]):
|
||||||
|
"""Create a dictionnary of occasional products"""
|
||||||
|
result = []
|
||||||
|
for contract_product in contract_products:
|
||||||
|
existing_id = find_dict_in_list(
|
||||||
|
result,
|
||||||
|
'shipment',
|
||||||
|
contract_product.shipment.id
|
||||||
|
)
|
||||||
|
if existing_id < 0:
|
||||||
|
result.append({
|
||||||
|
'shipment': contract_product.shipment,
|
||||||
|
'price': compute_product_price(
|
||||||
|
contract_product.product,
|
||||||
|
contract_product.quantity
|
||||||
|
),
|
||||||
|
'products': [{
|
||||||
|
'product': contract_product.product,
|
||||||
|
'quantity': contract_product.quantity
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
result[existing_id]['products'].append({
|
||||||
|
'product': contract_product.product,
|
||||||
|
'quantity': contract_product.quantity
|
||||||
|
})
|
||||||
|
result[existing_id]['price'] += compute_product_price(
|
||||||
|
contract_product.product,
|
||||||
|
contract_product.quantity
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def generate_products_prices(
|
||||||
|
occasionals: list[dict],
|
||||||
|
recurrents: list[dict],
|
||||||
|
shipments: list[models.ShipmentPublic]
|
||||||
|
):
|
||||||
|
recurrent_price = compute_recurrent_prices(
|
||||||
|
recurrents,
|
||||||
|
len(shipments)
|
||||||
|
)
|
||||||
|
occasional_price = compute_occasional_prices(occasionals)
|
||||||
|
price = recurrent_price + occasional_price
|
||||||
|
return {
|
||||||
|
'total': price,
|
||||||
|
'recurrent': recurrent_price,
|
||||||
|
'occasionnal': occasional_price
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
from sqlmodel import create_engine, SQLModel, Session
|
from sqlmodel import Session, SQLModel, create_engine
|
||||||
from src.settings import settings
|
from src.settings import settings
|
||||||
|
|
||||||
engine = create_engine(f'postgresql://{settings.db_user}:{settings.db_pass}@{settings.db_host}:5432/{settings.db_name}')
|
engine = create_engine(
|
||||||
|
f'postgresql://{settings.db_user}:{settings.db_pass}@{settings.db_host}:5432/{settings.db_name}')
|
||||||
|
|
||||||
|
|
||||||
def get_session():
|
def get_session():
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
|
||||||
def create_all_tables():
|
def create_all_tables():
|
||||||
SQLModel.metadata.create_all(engine)
|
SQLModel.metadata.create_all(engine)
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
|
"""Forms module exceptions"""
|
||||||
|
import logging
|
||||||
|
|
||||||
class FormServiceError(Exception):
|
class FormServiceError(Exception):
|
||||||
|
"""Form service exception"""
|
||||||
def __init__(self, message: str):
|
def __init__(self, message: str):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
logging.error('FormService : %s', message)
|
||||||
|
|
||||||
|
|
||||||
class UserNotFoundError(FormServiceError):
|
class UserNotFoundError(FormServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProductorNotFoundError(FormServiceError):
|
class ProductorNotFoundError(FormServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FormNotFoundError(FormServiceError):
|
class FormNotFoundError(FormServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FormCreateError(FormServiceError):
|
class FormCreateError(FormServiceError):
|
||||||
def __init__(self, message: str, field: str | None = None):
|
def __init__(self, message: str, field: str | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
import src.forms.service as service
|
|
||||||
import src.forms.exceptions as exceptions
|
import src.forms.exceptions as exceptions
|
||||||
|
import src.forms.service as service
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
|
||||||
router = APIRouter(prefix='/forms')
|
router = APIRouter(prefix='/forms')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.FormPublic])
|
@router.get('', response_model=list[models.FormPublic])
|
||||||
async def get_forms(
|
async def get_forms(
|
||||||
seasons: list[str] = Query([]),
|
seasons: list[str] = Query([]),
|
||||||
@@ -18,6 +18,7 @@ async def get_forms(
|
|||||||
):
|
):
|
||||||
return service.get_all(session, seasons, productors, current_season)
|
return service.get_all(session, seasons, productors, current_season)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/referents', response_model=list[models.FormPublic])
|
@router.get('/referents', response_model=list[models.FormPublic])
|
||||||
async def get_forms_filtered(
|
async def get_forms_filtered(
|
||||||
seasons: list[str] = Query([]),
|
seasons: list[str] = Query([]),
|
||||||
@@ -28,53 +29,103 @@ async def get_forms_filtered(
|
|||||||
):
|
):
|
||||||
return service.get_all(session, seasons, productors, current_season, user)
|
return service.get_all(session, seasons, productors, current_season, user)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.FormPublic)
|
|
||||||
async def get_form(id: int, session: Session = Depends(get_session)):
|
@router.get(
|
||||||
result = service.get_one(session, id)
|
'/{_id}/preview-delete',
|
||||||
if result is None:
|
response_model=list[models.DeleteDependency]
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('form'))
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('forms', 'delete')
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = service.get_delete_dependencies(
|
||||||
|
session,
|
||||||
|
_id
|
||||||
|
)
|
||||||
|
except exceptions.FormNotFoundError as error:
|
||||||
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{_id}', response_model=models.FormPublic)
|
||||||
|
async def get_form(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session)
|
||||||
|
):
|
||||||
|
result = service.get_one(session, _id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('form')
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.FormPublic)
|
@router.post('', response_model=models.FormPublic)
|
||||||
async def create_form(
|
async def create_form(
|
||||||
form: models.FormCreate,
|
form: models.FormCreate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, form=form):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('forms', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
form = service.create_one(session, form)
|
form = service.create_one(session, form)
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
except exceptions.UserNotFoundError as error:
|
except exceptions.UserNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
except exceptions.FormCreateError as error:
|
except exceptions.FormCreateError as error:
|
||||||
raise HTTPException(status_code=400, detail=str(error))
|
raise HTTPException(status_code=400, detail=str(error)) from error
|
||||||
return form
|
return form
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.FormPublic)
|
|
||||||
|
@router.put('/{_id}', response_model=models.FormPublic)
|
||||||
async def update_form(
|
async def update_form(
|
||||||
id: int, form: models.FormUpdate,
|
_id: int,
|
||||||
|
form: models.FormUpdate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('forms', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.update_one(session, id, form)
|
result = service.update_one(session, _id, form)
|
||||||
except exceptions.FormNotFoundError as error:
|
except exceptions.FormNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
except exceptions.UserNotFoundError as error:
|
except exceptions.UserNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.FormPublic)
|
|
||||||
|
@router.delete('/{_id}', response_model=models.FormPublic)
|
||||||
async def delete_form(
|
async def delete_form(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('forms', 'delete')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, _id)
|
||||||
except exceptions.FormNotFoundError as error:
|
except exceptions.FormNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from sqlmodel import Session, select
|
|
||||||
from sqlalchemy import func
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.forms.exceptions as exceptions
|
import src.forms.exceptions as exceptions
|
||||||
import src.messages as messages
|
from sqlalchemy import func
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from src import messages, models
|
||||||
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
@@ -14,24 +13,28 @@ def get_all(
|
|||||||
) -> list[models.FormPublic]:
|
) -> list[models.FormPublic]:
|
||||||
statement = select(models.Form)
|
statement = select(models.Form)
|
||||||
if user:
|
if user:
|
||||||
statement = statement\
|
statement = statement .join(
|
||||||
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
|
models.Productor,
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
models.Form.productor_id == models.Productor.id) .where(
|
||||||
.distinct()
|
models.Productor.type.in_(
|
||||||
|
[
|
||||||
|
r.name for r in user.roles])) .distinct()
|
||||||
if len(seasons) > 0:
|
if len(seasons) > 0:
|
||||||
statement = statement.where(models.Form.season.in_(seasons))
|
statement = statement.where(models.Form.season.in_(seasons))
|
||||||
if len(productors) > 0:
|
if len(productors) > 0:
|
||||||
statement = statement.join(models.Productor).where(models.Productor.name.in_(productors))
|
statement = statement.join(
|
||||||
|
models.Productor).where(
|
||||||
|
models.Productor.name.in_(productors))
|
||||||
if not user:
|
if not user:
|
||||||
statement = statement.where(models.Form.visible == True)
|
statement = statement.where(models.Form.visible)
|
||||||
if current_season:
|
if current_season:
|
||||||
subquery = (
|
subquery = (
|
||||||
select(
|
select(
|
||||||
models.Productor.type,
|
models.Productor.type,
|
||||||
func.max(models.Form.start).label("max_start")
|
func.max(models.Form.start).label("max_start")
|
||||||
)
|
)
|
||||||
.join(models.Form)\
|
.join(models.Form)
|
||||||
.group_by(models.Productor.type)\
|
.group_by(models.Productor.type)
|
||||||
.subquery()
|
.subquery()
|
||||||
)
|
)
|
||||||
statement = select(models.Form)\
|
statement = select(models.Form)\
|
||||||
@@ -41,18 +44,23 @@ def get_all(
|
|||||||
(models.Form.start == subquery.c.max_start)
|
(models.Form.start == subquery.c.max_start)
|
||||||
)
|
)
|
||||||
if not user:
|
if not user:
|
||||||
statement = statement.where(models.Form.visible == True)
|
statement = statement.where(models.Form.visible)
|
||||||
return session.exec(statement.order_by(models.Form.name)).all()
|
return session.exec(statement.order_by(models.Form.name)).all()
|
||||||
return session.exec(statement.order_by(models.Form.name)).all()
|
return session.exec(statement.order_by(models.Form.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_one(session: Session, form_id: int) -> models.FormPublic:
|
def get_one(session: Session, form_id: int) -> models.FormPublic:
|
||||||
return session.get(models.Form, form_id)
|
return session.get(models.Form, form_id)
|
||||||
|
|
||||||
|
|
||||||
def create_one(session: Session, form: models.FormCreate) -> models.FormPublic:
|
def create_one(session: Session, form: models.FormCreate) -> models.FormPublic:
|
||||||
if not form:
|
if not form:
|
||||||
raise exceptions.FormCreateError(messages.Messages.invalid_input('form', 'input cannot be None'))
|
raise exceptions.FormCreateError(
|
||||||
|
messages.Messages.invalid_input(
|
||||||
|
'form', 'input cannot be None'))
|
||||||
if not session.get(models.Productor, form.productor_id):
|
if not session.get(models.Productor, form.productor_id):
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
if not session.get(models.User, form.referer_id):
|
if not session.get(models.User, form.referer_id):
|
||||||
raise exceptions.UserNotFoundError(messages.Messages.not_found('user'))
|
raise exceptions.UserNotFoundError(messages.Messages.not_found('user'))
|
||||||
form_create = form.model_dump(exclude_unset=True)
|
form_create = form.model_dump(exclude_unset=True)
|
||||||
@@ -62,14 +70,20 @@ def create_one(session: Session, form: models.FormCreate) -> models.FormPublic:
|
|||||||
session.refresh(new_form)
|
session.refresh(new_form)
|
||||||
return new_form
|
return new_form
|
||||||
|
|
||||||
def update_one(session: Session, id: int, form: models.FormUpdate) -> models.FormPublic:
|
|
||||||
statement = select(models.Form).where(models.Form.id == id)
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
form: models.FormUpdate) -> models.FormPublic:
|
||||||
|
statement = select(models.Form).where(models.Form.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_form = result.first()
|
new_form = result.first()
|
||||||
if not new_form:
|
if not new_form:
|
||||||
raise exceptions.FormNotFoundError(messages.Messages.not_found('form'))
|
raise exceptions.FormNotFoundError(messages.Messages.not_found('form'))
|
||||||
if form.productor_id and not session.get(models.Productor, form.productor_id):
|
if form.productor_id and not session.get(
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
models.Productor, form.productor_id):
|
||||||
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
if form.referer_id and not session.get(models.User, form.referer_id):
|
if form.referer_id and not session.get(models.User, form.referer_id):
|
||||||
raise exceptions.UserNotFoundError(messages.Messages.not_found('user'))
|
raise exceptions.UserNotFoundError(messages.Messages.not_found('user'))
|
||||||
form_updates = form.model_dump(exclude_unset=True)
|
form_updates = form.model_dump(exclude_unset=True)
|
||||||
@@ -80,8 +94,9 @@ def update_one(session: Session, id: int, form: models.FormUpdate) -> models.For
|
|||||||
session.refresh(new_form)
|
session.refresh(new_form)
|
||||||
return new_form
|
return new_form
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.FormPublic:
|
|
||||||
statement = select(models.Form).where(models.Form.id == id)
|
def delete_one(session: Session, _id: int) -> models.FormPublic:
|
||||||
|
statement = select(models.Form).where(models.Form.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
form = result.first()
|
form = result.first()
|
||||||
if not form:
|
if not form:
|
||||||
@@ -91,10 +106,71 @@ def delete_one(session: Session, id: int) -> models.FormPublic:
|
|||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def is_allowed(session: Session, user: models.User, id: int) -> bool:
|
|
||||||
statement = select(models.Form)\
|
def get_delete_dependencies(
|
||||||
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
|
session: Session,
|
||||||
.where(models.Form.id == id)\
|
_id: int
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
) -> list[models.DeleteDependency]:
|
||||||
|
statement = select(models.Form).where(models.Form.id == _id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
form = result.first()
|
||||||
|
if not form:
|
||||||
|
raise exceptions.FormNotFoundError(messages.Messages.not_found('form'))
|
||||||
|
statement_shipment = (
|
||||||
|
select(models.Shipment)
|
||||||
|
.where(models.Shipment.form_id == _id)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
)
|
||||||
|
statement_contracts = (
|
||||||
|
select(models.Contract)
|
||||||
|
.where(models.Contract.form_id == _id)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
shipments = session.exec(statement_shipment).all()
|
||||||
|
contracts = session.exec(statement_contracts).all()
|
||||||
|
result = [
|
||||||
|
models.DeleteDependency(
|
||||||
|
name=sh.name,
|
||||||
|
id=sh.id,
|
||||||
|
type='shipment'
|
||||||
|
) for sh in shipments
|
||||||
|
] + [
|
||||||
|
models.DeleteDependency(
|
||||||
|
name=f'{co.firstname} {co.lastname}',
|
||||||
|
id=co.id,
|
||||||
|
type='contract'
|
||||||
|
) for co in contracts
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed(
|
||||||
|
session: Session,
|
||||||
|
user: models.User,
|
||||||
|
_id: int = None,
|
||||||
|
form: models.FormCreate = None
|
||||||
|
) -> bool:
|
||||||
|
if not _id and not form:
|
||||||
|
return False
|
||||||
|
if not _id:
|
||||||
|
statement = (
|
||||||
|
select(models.Productor)
|
||||||
|
.where(models.Productor.id == form.productor_id)
|
||||||
|
)
|
||||||
|
productor = session.exec(statement).first()
|
||||||
|
return productor.type in [r.name for r in user.roles]
|
||||||
|
statement = (
|
||||||
|
select(models.Form)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Form.productor_id == models.Productor.id
|
||||||
|
)
|
||||||
|
.where(models.Form.id == _id)
|
||||||
|
.where(
|
||||||
|
models.Productor.type.in_(
|
||||||
|
[r.name for r in user.roles]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
return len(session.exec(statement).all()) > 0
|
return len(session.exec(statement).all()) > 0
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
from sqlmodel import SQLModel
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from src.auth.auth import router as auth_router
|
||||||
from src.templates.templates import router as template_router
|
|
||||||
from src.contracts.contracts import router as contracts_router
|
from src.contracts.contracts import router as contracts_router
|
||||||
from src.forms.forms import router as forms_router
|
from src.forms.forms import router as forms_router
|
||||||
from src.productors.productors import router as productors_router
|
from src.productors.productors import router as productors_router
|
||||||
from src.products.products import router as products_router
|
from src.products.products import router as products_router
|
||||||
from src.users.users import router as users_router
|
|
||||||
from src.auth.auth import router as auth_router
|
|
||||||
from src.shipments.shipments import router as shipment_router
|
|
||||||
from src.settings import settings
|
from src.settings import settings
|
||||||
from src.database import engine, create_all_tables
|
from src.shipments.shipments import router as shipment_router
|
||||||
|
from src.templates.templates import router as template_router
|
||||||
|
from src.users.users import router as users_router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pdferror = 'An error occured during PDF generation please contact administrator'
|
pdferror = 'An error occured during PDF generation please contact administrator'
|
||||||
|
|
||||||
|
|
||||||
class Messages:
|
class Messages:
|
||||||
unauthorized = 'User is Unauthorized'
|
unauthorized = 'User is Unauthorized'
|
||||||
notauthenticated = 'User is not authenticated'
|
notauthenticated = 'User is not authenticated'
|
||||||
|
|||||||
@@ -1,99 +1,142 @@
|
|||||||
from sqlmodel import Field, SQLModel, Relationship, Column, LargeBinary
|
import datetime
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import datetime
|
|
||||||
|
from sqlmodel import Column, Field, LargeBinary, Relationship, SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteDependency(SQLModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
|
||||||
|
|
||||||
class ContractType(SQLModel, table=True):
|
class ContractType(SQLModel, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class UserContractTypeLink(SQLModel, table=True):
|
class UserContractTypeLink(SQLModel, table=True):
|
||||||
user_id: int = Field(foreign_key="user.id", primary_key=True)
|
user_id: int = Field(
|
||||||
contract_type_id: int = Field(foreign_key="contracttype.id", primary_key=True)
|
foreign_key='user.id',
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
contract_type_id: int = Field(
|
||||||
|
foreign_key='contracttype.id',
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserBase(SQLModel):
|
class UserBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
|
|
||||||
|
|
||||||
class UserPublic(UserBase):
|
class UserPublic(UserBase):
|
||||||
id: int
|
id: int
|
||||||
roles: list[ContractType]
|
roles: list[ContractType]
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase, table=True):
|
class User(UserBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
roles: list[ContractType] = Relationship(
|
roles: list[ContractType] = Relationship(
|
||||||
link_model=UserContractTypeLink
|
link_model=UserContractTypeLink
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(SQLModel):
|
class UserUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
email: str | None
|
email: str | None
|
||||||
role_names: list[str] | None
|
role_names: list[str] | None
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
role_names: list[str] | None
|
role_names: list[str] | None
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethodBase(SQLModel):
|
class PaymentMethodBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
details: str
|
details: str
|
||||||
max: int | None
|
max: int | None
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethod(PaymentMethodBase, table=True):
|
class PaymentMethod(PaymentMethodBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
productor_id: int = Field(foreign_key="productor.id", ondelete="CASCADE")
|
productor_id: int = Field(foreign_key='productor.id', ondelete='CASCADE')
|
||||||
productor: Optional["Productor"] = Relationship(
|
productor: Optional['Productor'] = Relationship(
|
||||||
back_populates="payment_methods",
|
back_populates='payment_methods',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethodPublic(PaymentMethodBase):
|
class PaymentMethodPublic(PaymentMethodBase):
|
||||||
id: int
|
id: int
|
||||||
productor: Optional["Productor"]
|
productor: Optional['Productor']
|
||||||
|
|
||||||
|
|
||||||
class ProductorBase(SQLModel):
|
class ProductorBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
address: str
|
address: str
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
|
||||||
class ProductorPublic(ProductorBase):
|
class ProductorPublic(ProductorBase):
|
||||||
id: int
|
id: int
|
||||||
products: list["Product"] = []
|
products: list['Product'] = Field(default_factory=list)
|
||||||
payment_methods: list["PaymentMethod"] = []
|
payment_methods: list['PaymentMethod'] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class Productor(ProductorBase, table=True):
|
class Productor(ProductorBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
products: list["Product"] = Relationship(
|
products: list['Product'] = Relationship(
|
||||||
back_populates='productor',
|
back_populates='productor',
|
||||||
sa_relationship_kwargs={
|
sa_relationship_kwargs={
|
||||||
"order_by": "Product.name"
|
'order_by': 'Product.name'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
payment_methods: list["PaymentMethod"] = Relationship(
|
payment_methods: list['PaymentMethod'] = Relationship(
|
||||||
back_populates="productor",
|
back_populates='productor',
|
||||||
cascade_delete=True
|
cascade_delete=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductorUpdate(SQLModel):
|
class ProductorUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
address: str | None
|
address: str | None
|
||||||
payment_methods: list["PaymentMethod"] = []
|
payment_methods: list['PaymentMethod'] = Field(default_factory=list)
|
||||||
type: str | None
|
type: str | None
|
||||||
|
|
||||||
|
|
||||||
class ProductorCreate(ProductorBase):
|
class ProductorCreate(ProductorBase):
|
||||||
payment_methods: list["PaymentMethod"] = []
|
payment_methods: list['PaymentMethod'] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class Unit(StrEnum):
|
class Unit(StrEnum):
|
||||||
GRAMS = "1"
|
GRAMS = '1'
|
||||||
KILO = "2"
|
KILO = '2'
|
||||||
PIECE = "3"
|
PIECE = '3'
|
||||||
|
|
||||||
|
|
||||||
class ProductType(StrEnum):
|
class ProductType(StrEnum):
|
||||||
OCCASIONAL = "1"
|
OCCASIONAL = '1'
|
||||||
RECCURENT = "2"
|
RECCURENT = '2'
|
||||||
|
|
||||||
|
|
||||||
class ShipmentProductLink(SQLModel, table=True):
|
class ShipmentProductLink(SQLModel, table=True):
|
||||||
shipment_id: Optional[int] = Field(default=None, foreign_key="shipment.id", primary_key=True)
|
shipment_id: Optional[int] = Field(
|
||||||
product_id: Optional[int] = Field(default=None, foreign_key="product.id", primary_key=True)
|
default=None,
|
||||||
|
foreign_key='shipment.id',
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
product_id: Optional[int] = Field(
|
||||||
|
default=None,
|
||||||
|
foreign_key='product.id',
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductBase(SQLModel):
|
class ProductBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
@@ -103,17 +146,31 @@ class ProductBase(SQLModel):
|
|||||||
quantity: float | None
|
quantity: float | None
|
||||||
quantity_unit: str | None
|
quantity_unit: str | None
|
||||||
type: ProductType
|
type: ProductType
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
productor_id: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
foreign_key='productor.id'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductPublic(ProductBase):
|
class ProductPublic(ProductBase):
|
||||||
id: int
|
id: int
|
||||||
productor: Productor | None
|
productor: Productor | None
|
||||||
shipments: list["Shipment"] | None
|
shipments: list['Shipment'] | None
|
||||||
|
|
||||||
|
|
||||||
class Product(ProductBase, table=True):
|
class Product(ProductBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(
|
||||||
shipments: list["Shipment"] = Relationship(back_populates="products", link_model=ShipmentProductLink)
|
default=None,
|
||||||
productor: Optional[Productor] = Relationship(back_populates="products")
|
primary_key=True
|
||||||
|
)
|
||||||
|
shipments: list['Shipment'] = Relationship(
|
||||||
|
back_populates='products',
|
||||||
|
link_model=ShipmentProductLink
|
||||||
|
)
|
||||||
|
productor: Optional[Productor] = Relationship(
|
||||||
|
back_populates='products'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductUpdate(SQLModel):
|
class ProductUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
@@ -125,41 +182,46 @@ class ProductUpdate(SQLModel):
|
|||||||
productor_id: int | None
|
productor_id: int | None
|
||||||
type: ProductType | None
|
type: ProductType | None
|
||||||
|
|
||||||
|
|
||||||
class ProductCreate(ProductBase):
|
class ProductCreate(ProductBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FormBase(SQLModel):
|
class FormBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
productor_id: int | None = Field(default=None, foreign_key="productor.id")
|
productor_id: int | None = Field(default=None, foreign_key='productor.id')
|
||||||
referer_id: int | None = Field(default=None, foreign_key="user.id")
|
referer_id: int | None = Field(default=None, foreign_key='user.id')
|
||||||
season: str
|
season: str
|
||||||
start: datetime.date
|
start: datetime.date
|
||||||
end: datetime.date
|
end: datetime.date
|
||||||
minimum_shipment_value: float | None
|
minimum_shipment_value: float | None
|
||||||
visible: bool
|
visible: bool
|
||||||
|
|
||||||
|
|
||||||
class FormPublic(FormBase):
|
class FormPublic(FormBase):
|
||||||
id: int
|
id: int
|
||||||
productor: ProductorPublic | None
|
productor: ProductorPublic | None
|
||||||
referer: User | None
|
referer: User | None
|
||||||
shipments: list["ShipmentPublic"] = []
|
shipments: list['ShipmentPublic'] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class Form(FormBase, table=True):
|
class Form(FormBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
productor: Optional['Productor'] = Relationship()
|
productor: Optional['Productor'] = Relationship()
|
||||||
referer: Optional['User'] = Relationship()
|
referer: Optional['User'] = Relationship()
|
||||||
shipments: list["Shipment"] = Relationship(
|
shipments: list['Shipment'] = Relationship(
|
||||||
back_populates="form",
|
back_populates='form',
|
||||||
cascade_delete=True,
|
cascade_delete=True,
|
||||||
sa_relationship_kwargs={
|
sa_relationship_kwargs={
|
||||||
"order_by": "Shipment.name"
|
'order_by': 'Shipment.name'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
contracts: list["Contract"] = Relationship(
|
contracts: list['Contract'] = Relationship(
|
||||||
back_populates="form",
|
back_populates='form',
|
||||||
cascade_delete=True
|
cascade_delete=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FormUpdate(SQLModel):
|
class FormUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
productor_id: int | None
|
productor_id: int | None
|
||||||
@@ -170,35 +232,44 @@ class FormUpdate(SQLModel):
|
|||||||
minimum_shipment_value: float | None
|
minimum_shipment_value: float | None
|
||||||
visible: bool | None
|
visible: bool | None
|
||||||
|
|
||||||
|
|
||||||
class FormCreate(FormBase):
|
class FormCreate(FormBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TemplateBase(SQLModel):
|
class TemplateBase(SQLModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TemplatePublic(TemplateBase):
|
class TemplatePublic(TemplateBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
|
|
||||||
class Template(TemplateBase, table=True):
|
class Template(TemplateBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class TemplateUpdate(SQLModel):
|
class TemplateUpdate(SQLModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TemplateCreate(TemplateBase):
|
class TemplateCreate(TemplateBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ChequeBase(SQLModel):
|
class ChequeBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
|
|
||||||
class Cheque(ChequeBase, table=True):
|
class Cheque(ChequeBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
contract_id: int = Field(foreign_key="contract.id", ondelete="CASCADE")
|
contract_id: int = Field(foreign_key='contract.id', ondelete='CASCADE')
|
||||||
contract: Optional["Contract"] = Relationship(
|
contract: Optional['Contract'] = Relationship(
|
||||||
back_populates="cheques",
|
back_populates='cheques',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ContractBase(SQLModel):
|
class ContractBase(SQLModel):
|
||||||
firstname: str
|
firstname: str
|
||||||
lastname: str
|
lastname: str
|
||||||
@@ -207,105 +278,122 @@ class ContractBase(SQLModel):
|
|||||||
payment_method: str
|
payment_method: str
|
||||||
cheque_quantity: int
|
cheque_quantity: int
|
||||||
|
|
||||||
|
|
||||||
class Contract(ContractBase, table=True):
|
class Contract(ContractBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
form_id: int = Field(
|
form_id: int = Field(
|
||||||
foreign_key="form.id",
|
foreign_key='form.id',
|
||||||
nullable=False,
|
nullable=False,
|
||||||
ondelete="CASCADE"
|
ondelete='CASCADE'
|
||||||
)
|
)
|
||||||
products: list["ContractProduct"] = Relationship(
|
products: list['ContractProduct'] = Relationship(
|
||||||
back_populates="contract",
|
back_populates='contract',
|
||||||
cascade_delete=True
|
cascade_delete=True
|
||||||
)
|
)
|
||||||
form: Optional[Form] = Relationship(back_populates="contracts")
|
form: Form = Relationship(back_populates='contracts')
|
||||||
cheques: list[Cheque] = Relationship(
|
cheques: list[Cheque] = Relationship(
|
||||||
back_populates="contract",
|
back_populates='contract',
|
||||||
cascade_delete=True
|
cascade_delete=True
|
||||||
)
|
)
|
||||||
file: bytes = Field(sa_column=Column(LargeBinary))
|
file: bytes = Field(sa_column=Column(LargeBinary))
|
||||||
total_price: float | None
|
total_price: float | None
|
||||||
|
|
||||||
|
|
||||||
class ContractCreate(ContractBase):
|
class ContractCreate(ContractBase):
|
||||||
products: list["ContractProductCreate"] = []
|
products: list['ContractProductCreate'] = Field(default_factory=list)
|
||||||
cheques: list["Cheque"] = []
|
cheques: list['Cheque'] = Field(default_factory=list)
|
||||||
form_id: int
|
form_id: int
|
||||||
|
|
||||||
|
|
||||||
class ContractUpdate(SQLModel):
|
class ContractUpdate(SQLModel):
|
||||||
file: bytes
|
file: bytes
|
||||||
|
|
||||||
|
|
||||||
class ContractPublic(ContractBase):
|
class ContractPublic(ContractBase):
|
||||||
id: int
|
id: int
|
||||||
products: list["ContractProduct"] = []
|
products: list['ContractProduct'] = Field(default_factory=list)
|
||||||
form: Form
|
form: Form
|
||||||
total_price: float | None
|
total_price: float | None
|
||||||
# file: bytes
|
# file: bytes
|
||||||
|
|
||||||
|
|
||||||
class ContractProductBase(SQLModel):
|
class ContractProductBase(SQLModel):
|
||||||
product_id: int = Field(
|
product_id: int = Field(
|
||||||
foreign_key="product.id",
|
foreign_key='product.id',
|
||||||
nullable=False,
|
nullable=False,
|
||||||
ondelete="CASCADE"
|
ondelete='CASCADE'
|
||||||
)
|
)
|
||||||
shipment_id: int | None = Field(
|
shipment_id: int | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
foreign_key="shipment.id",
|
foreign_key='shipment.id',
|
||||||
nullable=True,
|
nullable=True,
|
||||||
ondelete="CASCADE"
|
ondelete='CASCADE'
|
||||||
)
|
)
|
||||||
quantity: float
|
quantity: float
|
||||||
|
|
||||||
|
|
||||||
class ContractProduct(ContractProductBase, table=True):
|
class ContractProduct(ContractProductBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
contract_id: int = Field(
|
contract_id: int = Field(
|
||||||
foreign_key="contract.id",
|
foreign_key='contract.id',
|
||||||
nullable=False,
|
nullable=False,
|
||||||
ondelete="CASCADE"
|
ondelete='CASCADE'
|
||||||
)
|
)
|
||||||
contract: Optional["Contract"] = Relationship(back_populates="products")
|
contract: Optional['Contract'] = Relationship(back_populates='products')
|
||||||
product: Optional["Product"] = Relationship()
|
product: Optional['Product'] = Relationship()
|
||||||
shipment: Optional["Shipment"] = Relationship()
|
shipment: Optional['Shipment'] = Relationship()
|
||||||
|
|
||||||
|
|
||||||
class ContractProductPublic(ContractProductBase):
|
class ContractProductPublic(ContractProductBase):
|
||||||
id: int
|
id: int
|
||||||
quantity: float
|
quantity: float
|
||||||
contract: Contract
|
contract: Contract
|
||||||
product: Product
|
product: Product
|
||||||
shipment: Optional["Shipment"]
|
shipment: Optional['Shipment']
|
||||||
|
|
||||||
|
|
||||||
class ContractProductCreate(ContractProductBase):
|
class ContractProductCreate(ContractProductBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContractProductUpdate(ContractProductBase):
|
class ContractProductUpdate(ContractProductBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ShipmentBase(SQLModel):
|
class ShipmentBase(SQLModel):
|
||||||
name: str
|
name: str
|
||||||
date: datetime.date
|
date: datetime.date
|
||||||
form_id: int | None = Field(default=None, foreign_key="form.id", ondelete="CASCADE")
|
form_id: int | None = Field(
|
||||||
|
default=None,
|
||||||
|
foreign_key='form.id',
|
||||||
|
ondelete='CASCADE')
|
||||||
|
|
||||||
|
|
||||||
class ShipmentPublic(ShipmentBase):
|
class ShipmentPublic(ShipmentBase):
|
||||||
id: int
|
id: int
|
||||||
products: list[Product] = []
|
products: list[Product] = Field(default_factory=list)
|
||||||
form: Form | None
|
form: Form | None
|
||||||
|
|
||||||
|
|
||||||
class Shipment(ShipmentBase, table=True):
|
class Shipment(ShipmentBase, table=True):
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
products: list[Product] = Relationship(
|
products: list[Product] = Relationship(
|
||||||
back_populates="shipments",
|
back_populates='shipments',
|
||||||
link_model=ShipmentProductLink,
|
link_model=ShipmentProductLink,
|
||||||
sa_relationship_kwargs={
|
sa_relationship_kwargs={
|
||||||
"order_by": "Product.name"
|
'order_by': 'Product.name'
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
form: Optional[Form] = Relationship(back_populates="shipments")
|
form: Optional[Form] = Relationship(back_populates='shipments')
|
||||||
|
|
||||||
|
|
||||||
class ShipmentUpdate(SQLModel):
|
class ShipmentUpdate(SQLModel):
|
||||||
name: str | None
|
name: str | None
|
||||||
date: datetime.date | None
|
date: datetime.date | None
|
||||||
product_ids: list[int] | None = []
|
product_ids: list[int] | None = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class ShipmentCreate(ShipmentBase):
|
class ShipmentCreate(ShipmentBase):
|
||||||
product_ids: list[int] = []
|
product_ids: list[int] = Field(default_factory=list)
|
||||||
form_id: int
|
form_id: int
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class ProductorServiceError(Exception):
|
class ProductorServiceError(Exception):
|
||||||
def __init__(self, message: str):
|
def __init__(self, message: str):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
logging.error('ProductorService : %s', message)
|
||||||
|
|
||||||
|
|
||||||
class ProductorNotFoundError(ProductorServiceError):
|
class ProductorNotFoundError(ProductorServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProductorCreateError(ProductorServiceError):
|
class ProductorCreateError(ProductorServiceError):
|
||||||
def __init__(self, message: str, field: str | None = None):
|
def __init__(self, message: str, field: str | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
import src.productors.service as service
|
from src import messages, models
|
||||||
import src.productors.exceptions as exceptions
|
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
from src.productors import exceptions, service
|
||||||
|
|
||||||
router = APIRouter(prefix='/productors')
|
router = APIRouter(prefix='/productors')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.ProductorPublic])
|
@router.get('', response_model=list[models.ProductorPublic])
|
||||||
def get_productors(
|
def get_productors(
|
||||||
names: list[str] = Query([]),
|
names: list[str] = Query([]),
|
||||||
@@ -18,49 +17,100 @@ def get_productors(
|
|||||||
):
|
):
|
||||||
return service.get_all(session, user, names, types)
|
return service.get_all(session, user, names, types)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ProductorPublic)
|
|
||||||
|
@router.get(
|
||||||
|
'/{_id}/preview-delete',
|
||||||
|
response_model=list[models.DeleteDependency]
|
||||||
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('productors', 'delete')
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = service.get_delete_dependencies(
|
||||||
|
session,
|
||||||
|
_id
|
||||||
|
)
|
||||||
|
except exceptions.ProductorNotFoundError as error:
|
||||||
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{_id}', response_model=models.ProductorPublic)
|
||||||
def get_productor(
|
def get_productor(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
result = service.get_one(session, id)
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('productor', 'get')
|
||||||
|
)
|
||||||
|
result = service.get_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('productor'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('productor')
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.ProductorPublic)
|
@router.post('', response_model=models.ProductorPublic)
|
||||||
def create_productor(
|
def create_productor(
|
||||||
productor: models.ProductorCreate,
|
productor: models.ProductorCreate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, productor=productor):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('productor', 'create')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.create_one(session, productor)
|
result = service.create_one(session, productor)
|
||||||
except exceptions.ProductorCreateError as error:
|
except exceptions.ProductorCreateError as error:
|
||||||
raise HTTPException(status_code=400, detail=str(error))
|
raise HTTPException(status_code=400, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.ProductorPublic)
|
|
||||||
|
@router.put('/{_id}', response_model=models.ProductorPublic)
|
||||||
def update_productor(
|
def update_productor(
|
||||||
id: int, productor: models.ProductorUpdate,
|
_id: int, productor: models.ProductorUpdate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('productor', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.update_one(session, id, productor)
|
result = service.update_one(session, _id, productor)
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.ProductorPublic)
|
|
||||||
|
@router.delete('/{_id}', response_model=models.ProductorPublic)
|
||||||
def delete_productor(
|
def delete_productor(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('productor', 'delete')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, _id)
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import src.models as models
|
from src import messages, models
|
||||||
import src.productors.exceptions as exceptions
|
from src.productors import exceptions
|
||||||
import src.messages as messages
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
@@ -18,13 +18,20 @@ def get_all(
|
|||||||
statement = statement.where(models.Productor.type.in_(types))
|
statement = statement.where(models.Productor.type.in_(types))
|
||||||
return session.exec(statement.order_by(models.Productor.name)).all()
|
return session.exec(statement.order_by(models.Productor.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
def get_one(session: Session, productor_id: int) -> models.ProductorPublic:
|
||||||
return session.get(models.Productor, productor_id)
|
return session.get(models.Productor, productor_id)
|
||||||
|
|
||||||
def create_one(session: Session, productor: models.ProductorCreate) -> models.ProductorPublic:
|
|
||||||
|
def create_one(
|
||||||
|
session: Session,
|
||||||
|
productor: models.ProductorCreate) -> models.ProductorPublic:
|
||||||
if not productor:
|
if not productor:
|
||||||
raise exceptions.ProductorCreateError(messages.Messages.invalid_input('productor', 'input cannot be None'))
|
raise exceptions.ProductorCreateError(
|
||||||
productor_create = productor.model_dump(exclude_unset=True, exclude='payment_methods')
|
messages.Messages.invalid_input(
|
||||||
|
'productor', 'input cannot be None'))
|
||||||
|
productor_create = productor.model_dump(
|
||||||
|
exclude_unset=True, exclude='payment_methods')
|
||||||
new_productor = models.Productor(**productor_create)
|
new_productor = models.Productor(**productor_create)
|
||||||
|
|
||||||
new_productor.payment_methods = [
|
new_productor.payment_methods = [
|
||||||
@@ -39,12 +46,18 @@ def create_one(session: Session, productor: models.ProductorCreate) -> models.Pr
|
|||||||
session.refresh(new_productor)
|
session.refresh(new_productor)
|
||||||
return new_productor
|
return new_productor
|
||||||
|
|
||||||
def update_one(session: Session, id: int, productor: models.ProductorUpdate) -> models.ProductorPublic:
|
|
||||||
statement = select(models.Productor).where(models.Productor.id == id)
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
productor: models.ProductorUpdate
|
||||||
|
) -> models.ProductorPublic:
|
||||||
|
statement = select(models.Productor).where(models.Productor.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_productor = result.first()
|
new_productor = result.first()
|
||||||
if not new_productor:
|
if not new_productor:
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
|
|
||||||
productor_updates = productor.model_dump(exclude_unset=True)
|
productor_updates = productor.model_dump(exclude_unset=True)
|
||||||
if 'payment_methods' in productor_updates:
|
if 'payment_methods' in productor_updates:
|
||||||
@@ -67,13 +80,60 @@ def update_one(session: Session, id: int, productor: models.ProductorUpdate) ->
|
|||||||
session.refresh(new_productor)
|
session.refresh(new_productor)
|
||||||
return new_productor
|
return new_productor
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.ProductorPublic:
|
|
||||||
statement = select(models.Productor).where(models.Productor.id == id)
|
def delete_one(session: Session, _id: int) -> models.ProductorPublic:
|
||||||
|
statement = select(models.Productor).where(models.Productor.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
productor = result.first()
|
productor = result.first()
|
||||||
if not productor:
|
if not productor:
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
result = models.ProductorPublic.model_validate(productor)
|
result = models.ProductorPublic.model_validate(productor)
|
||||||
session.delete(productor)
|
session.delete(productor)
|
||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_delete_dependencies(
|
||||||
|
session: Session,
|
||||||
|
_id: int
|
||||||
|
) -> list[models.DeleteDependency]:
|
||||||
|
statement = select(models.Productor).where(models.Productor.id == _id)
|
||||||
|
result = session.exec(statement)
|
||||||
|
productor = result.first()
|
||||||
|
if not productor:
|
||||||
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
|
products_statement = (
|
||||||
|
select(models.Product)
|
||||||
|
.where(models.Product.productor_id == _id)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
products = session.exec(products_statement).all()
|
||||||
|
result = [
|
||||||
|
models.DeleteDependency(
|
||||||
|
name=pro.name,
|
||||||
|
id=pro.id,
|
||||||
|
type='product'
|
||||||
|
) for pro in products
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed(
|
||||||
|
session: Session,
|
||||||
|
user: models.User,
|
||||||
|
_id: int = None,
|
||||||
|
productor: models.ProductorCreate = None
|
||||||
|
) -> bool:
|
||||||
|
if not _id and not productor:
|
||||||
|
return False
|
||||||
|
if not _id:
|
||||||
|
return productor.type in [r.name for r in user.roles]
|
||||||
|
statement = (
|
||||||
|
select(models.Productor)
|
||||||
|
.where(models.Productor.id == _id)
|
||||||
|
.where(models.Productor.type.in_([r.name for r in user.roles]))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return len(session.exec(statement).all()) > 0
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ class ProductServiceError(Exception):
|
|||||||
def __init__(self, message: str):
|
def __init__(self, message: str):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
class ProductorNotFoundError(ProductServiceError):
|
class ProductorNotFoundError(ProductServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProductNotFoundError(ProductServiceError):
|
class ProductNotFoundError(ProductServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProductCreateError(ProductServiceError):
|
class ProductCreateError(ProductServiceError):
|
||||||
def __init__(self, message: str, field: str | None = None):
|
def __init__(self, message: str, field: str | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
import src.products.service as service
|
import src.products.service as service
|
||||||
import src.products.exceptions as exceptions
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
from src.products import exceptions
|
||||||
|
|
||||||
router = APIRouter(prefix='/products')
|
router = APIRouter(prefix='/products')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.ProductPublic], )
|
@router.get('', response_model=list[models.ProductPublic], )
|
||||||
def get_products(
|
def get_products(
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
@@ -25,53 +25,110 @@ def get_products(
|
|||||||
types,
|
types,
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ProductPublic)
|
|
||||||
|
@router.get(
|
||||||
|
'/{_id}/preview-delete',
|
||||||
|
response_model=list[models.DeleteDependency]
|
||||||
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('product', 'delete')
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{_id}', response_model=models.ProductPublic)
|
||||||
def get_product(
|
def get_product(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
result = service.get_one(session, id)
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('product', 'create')
|
||||||
|
)
|
||||||
|
result = service.get_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('product'))
|
raise HTTPException(status_code=404,
|
||||||
|
detail=messages.Messages.not_found('product'))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.ProductPublic)
|
@router.post('', response_model=models.ProductPublic)
|
||||||
def create_product(
|
def create_product(
|
||||||
product: models.ProductCreate,
|
product: models.ProductCreate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, product=product):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('product', 'create')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.create_one(session, product)
|
result = service.create_one(session, product)
|
||||||
except exceptions.ProductCreateError as error:
|
except exceptions.ProductCreateError as error:
|
||||||
raise HTTPException(status_code=400, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.ProductPublic)
|
|
||||||
|
@router.put('/{_id}', response_model=models.ProductPublic)
|
||||||
def update_product(
|
def update_product(
|
||||||
id: int, product: models.ProductUpdate,
|
_id: int, product: models.ProductUpdate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('product', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.update_one(session, id, product)
|
result = service.update_one(session, _id, product)
|
||||||
except exceptions.ProductNotFoundError as error:
|
except exceptions.ProductNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
except exceptions.ProductorNotFoundError as error:
|
except exceptions.ProductorNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.ProductPublic)
|
|
||||||
|
@router.delete('/{_id}', response_model=models.ProductPublic)
|
||||||
def delete_product(
|
def delete_product(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('product', 'delete')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, _id)
|
||||||
except exceptions.ProductNotFoundError as error:
|
except exceptions.ProductNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import src.models as models
|
from src import messages, models
|
||||||
import src.products.exceptions as exceptions
|
from src.products import exceptions
|
||||||
import src.messages as messages
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
@@ -10,10 +10,13 @@ def get_all(
|
|||||||
productors: list[str],
|
productors: list[str],
|
||||||
types: list[str],
|
types: list[str],
|
||||||
) -> list[models.ProductPublic]:
|
) -> list[models.ProductPublic]:
|
||||||
statement = select(models.Product)\
|
statement = select(
|
||||||
.join(models.Productor, models.Product.productor_id == models.Productor.id)\
|
models.Product) .join(
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
models.Productor,
|
||||||
.distinct()
|
models.Product.productor_id == models.Productor.id) .where(
|
||||||
|
models.Productor.type.in_(
|
||||||
|
[
|
||||||
|
r.name for r in user.roles])) .distinct()
|
||||||
if len(names) > 0:
|
if len(names) > 0:
|
||||||
statement = statement.where(models.Product.name.in_(names))
|
statement = statement.where(models.Product.name.in_(names))
|
||||||
if len(productors) > 0:
|
if len(productors) > 0:
|
||||||
@@ -22,14 +25,25 @@ def get_all(
|
|||||||
statement = statement.where(models.Product.type.in_(types))
|
statement = statement.where(models.Product.type.in_(types))
|
||||||
return session.exec(statement.order_by(models.Product.name)).all()
|
return session.exec(statement.order_by(models.Product.name)).all()
|
||||||
|
|
||||||
def get_one(session: Session, product_id: int) -> models.ProductPublic:
|
|
||||||
|
def get_one(
|
||||||
|
session: Session,
|
||||||
|
product_id: int,
|
||||||
|
) -> models.ProductPublic:
|
||||||
return session.get(models.Product, product_id)
|
return session.get(models.Product, product_id)
|
||||||
|
|
||||||
def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic:
|
|
||||||
|
def create_one(
|
||||||
|
session: Session,
|
||||||
|
product: models.ProductCreate,
|
||||||
|
) -> models.ProductPublic:
|
||||||
if not product:
|
if not product:
|
||||||
raise exceptions.ProductCreateError(messages.Messages.invalid_input('product', 'input cannot be None'))
|
raise exceptions.ProductCreateError(
|
||||||
|
messages.Messages.invalid_input(
|
||||||
|
'product', 'input cannot be None'))
|
||||||
if not session.get(models.Productor, product.productor_id):
|
if not session.get(models.Productor, product.productor_id):
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
product_create = product.model_dump(exclude_unset=True)
|
product_create = product.model_dump(exclude_unset=True)
|
||||||
new_product = models.Product(**product_create)
|
new_product = models.Product(**product_create)
|
||||||
session.add(new_product)
|
session.add(new_product)
|
||||||
@@ -37,14 +51,22 @@ def create_one(session: Session, product: models.ProductCreate) -> models.Produc
|
|||||||
session.refresh(new_product)
|
session.refresh(new_product)
|
||||||
return new_product
|
return new_product
|
||||||
|
|
||||||
def update_one(session: Session, id: int, product: models.ProductUpdate) -> models.ProductPublic:
|
|
||||||
statement = select(models.Product).where(models.Product.id == id)
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
product: models.ProductUpdate
|
||||||
|
) -> models.ProductPublic:
|
||||||
|
statement = select(models.Product).where(models.Product.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_product = result.first()
|
new_product = result.first()
|
||||||
if not new_product:
|
if not new_product:
|
||||||
raise exceptions.ProductNotFoundError(messages.Messages.not_found('product'))
|
raise exceptions.ProductNotFoundError(
|
||||||
if product.productor_id and not session.get(models.Productor, product.productor_id):
|
messages.Messages.not_found('product'))
|
||||||
raise exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
if product.productor_id and not session.get(
|
||||||
|
models.Productor, product.productor_id):
|
||||||
|
raise exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor'))
|
||||||
|
|
||||||
product_updates = product.model_dump(exclude_unset=True)
|
product_updates = product.model_dump(exclude_unset=True)
|
||||||
for key, value in product_updates.items():
|
for key, value in product_updates.items():
|
||||||
@@ -55,13 +77,46 @@ def update_one(session: Session, id: int, product: models.ProductUpdate) -> mode
|
|||||||
session.refresh(new_product)
|
session.refresh(new_product)
|
||||||
return new_product
|
return new_product
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.ProductPublic:
|
|
||||||
statement = select(models.Product).where(models.Product.id == id)
|
def delete_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int
|
||||||
|
) -> models.ProductPublic:
|
||||||
|
statement = select(models.Product).where(models.Product.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
product = result.first()
|
product = result.first()
|
||||||
if not product:
|
if not product:
|
||||||
raise exceptions.ProductNotFoundError(messages.Messages.not_found('product'))
|
raise exceptions.ProductNotFoundError(
|
||||||
|
messages.Messages.not_found('product'))
|
||||||
result = models.ProductPublic.model_validate(product)
|
result = models.ProductPublic.model_validate(product)
|
||||||
session.delete(product)
|
session.delete(product)
|
||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed(
|
||||||
|
session: Session,
|
||||||
|
user: models.User,
|
||||||
|
_id: int = None,
|
||||||
|
product: models.ProductCreate = None,
|
||||||
|
) -> bool:
|
||||||
|
if not _id and not product:
|
||||||
|
return False
|
||||||
|
if not _id:
|
||||||
|
statement = (
|
||||||
|
select(models.Productor)
|
||||||
|
.where(models.Productor.id == product.productor_id)
|
||||||
|
)
|
||||||
|
productor = session.exec(statement).first()
|
||||||
|
return productor.type in [r.name for r in user.roles]
|
||||||
|
statement = (
|
||||||
|
select(models.Product)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Product.productor_id == models.Productor.id
|
||||||
|
)
|
||||||
|
.where(models.Product.id == _id)
|
||||||
|
.where(models.Productor.type.in_([r.name for r in user.roles]))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return len(session.exec(statement).all()) > 0
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
origins: str
|
origins: str
|
||||||
db_host: str
|
db_host: str
|
||||||
@@ -20,10 +21,21 @@ class Settings(BaseSettings):
|
|||||||
env_file='../.env'
|
env_file='../.env'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
AUTH_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/auth"
|
AUTH_URL = (
|
||||||
TOKEN_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/token"
|
f'{settings.keycloak_server}/realms/'
|
||||||
ISSUER = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}"
|
f'{settings.keycloak_realm}/protocol/openid-connect/auth'
|
||||||
JWKS_URL = f"{ISSUER}/protocol/openid-connect/certs"
|
)
|
||||||
LOGOUT_URL = f'{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/logout'
|
TOKEN_URL = (
|
||||||
|
f'{settings.keycloak_server}/realms/'
|
||||||
|
f'{settings.keycloak_realm}/protocol/openid-connect/token'
|
||||||
|
)
|
||||||
|
|
||||||
|
ISSUER = f'{settings.keycloak_server}/realms/{settings.keycloak_realm}'
|
||||||
|
JWKS_URL = f'{ISSUER}/protocol/openid-connect/certs'
|
||||||
|
LOGOUT_URL = (
|
||||||
|
f'{settings.keycloak_server}/realms/'
|
||||||
|
f'{settings.keycloak_realm}/protocol/openid-connect/logout'
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class ShipmentServiceError(Exception):
|
class ShipmentServiceError(Exception):
|
||||||
def __init__(self, message: str):
|
def __init__(self, message: str):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
logging.error('ShipmentService : %s', message)
|
||||||
|
|
||||||
|
|
||||||
class ShipmentNotFoundError(ShipmentServiceError):
|
class ShipmentNotFoundError(ShipmentServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ShipmentCreateError(ShipmentServiceError):
|
class ShipmentCreateError(ShipmentServiceError):
|
||||||
def __init__(self, message: str, field: str | None = None):
|
def __init__(self, message: str, field: str | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -1,58 +1,110 @@
|
|||||||
from sqlmodel import Session, select
|
# pylint: disable=E1101
|
||||||
import src.models as models
|
|
||||||
import src.shipments.exceptions as exceptions
|
|
||||||
import src.messages as messages
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
import src.shipments.exceptions as exceptions
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from src import messages, models
|
||||||
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
user: models.User,
|
user: models.User,
|
||||||
names: list[str],
|
names: list[str] = None,
|
||||||
dates: list[str],
|
dates: list[str] = None,
|
||||||
forms: list[str]
|
forms: list[str] = None
|
||||||
) -> list[models.ShipmentPublic]:
|
) -> list[models.ShipmentPublic]:
|
||||||
statement = select(models.Shipment)\
|
statement = (
|
||||||
.join(models.Form, models.Shipment.form_id == models.Form.id)\
|
select(models.Shipment)
|
||||||
.join(models.Productor, models.Form.productor_id == models.Productor.id)\
|
.join(
|
||||||
.where(models.Productor.type.in_([r.name for r in user.roles]))\
|
models.Form,
|
||||||
|
models.Shipment.form_id == models.Form.id)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Form.productor_id == models.Productor.id)
|
||||||
|
.where(
|
||||||
|
models.Productor.type.in_(
|
||||||
|
[r.name for r in user.roles]
|
||||||
|
)
|
||||||
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
if len(names) > 0:
|
)
|
||||||
|
if names and len(names) > 0:
|
||||||
statement = statement.where(models.Shipment.name.in_(names))
|
statement = statement.where(models.Shipment.name.in_(names))
|
||||||
if len(dates) > 0:
|
if dates and len(dates) > 0:
|
||||||
statement = statement.where(models.Shipment.date.in_(list(map(lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), dates))))
|
statement = statement.where(
|
||||||
if len(forms) > 0:
|
models.Shipment.date.in_(
|
||||||
|
list(map(
|
||||||
|
lambda x: datetime.datetime.strptime(
|
||||||
|
x, '%Y-%m-%d').date(),
|
||||||
|
dates
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if forms and len(forms) > 0:
|
||||||
statement = statement.where(models.Form.name.in_(forms))
|
statement = statement.where(models.Form.name.in_(forms))
|
||||||
return session.exec(statement.order_by(models.Shipment.name)).all()
|
return session.exec(statement.order_by(models.Shipment.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
|
def get_one(session: Session, shipment_id: int) -> models.ShipmentPublic:
|
||||||
return session.get(models.Shipment, shipment_id)
|
return session.get(models.Shipment, shipment_id)
|
||||||
|
|
||||||
def create_one(session: Session, shipment: models.ShipmentCreate) -> models.ShipmentPublic:
|
|
||||||
|
def create_one(
|
||||||
|
session: Session,
|
||||||
|
shipment: models.ShipmentCreate) -> models.ShipmentPublic:
|
||||||
if shipment is None:
|
if shipment is None:
|
||||||
raise exceptions.ShipmentCreateError(messages.Messages.invalid_input('shipment', 'input cannot be None'))
|
raise exceptions.ShipmentCreateError(
|
||||||
products = session.exec(select(models.Product).where(models.Product.id.in_(shipment.product_ids))).all()
|
messages.Messages.invalid_input(
|
||||||
shipment_create = shipment.model_dump(exclude_unset=True, exclude={'product_ids'})
|
'shipment', 'input cannot be None'))
|
||||||
|
products = session.exec(
|
||||||
|
select(models.Product)
|
||||||
|
.where(
|
||||||
|
models.Product.id.in_(
|
||||||
|
shipment.product_ids
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
shipment_create = shipment.model_dump(
|
||||||
|
exclude_unset=True, exclude={'product_ids'}
|
||||||
|
)
|
||||||
new_shipment = models.Shipment(**shipment_create, products=products)
|
new_shipment = models.Shipment(**shipment_create, products=products)
|
||||||
session.add(new_shipment)
|
session.add(new_shipment)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(new_shipment)
|
session.refresh(new_shipment)
|
||||||
return new_shipment
|
return new_shipment
|
||||||
|
|
||||||
def update_one(session: Session, id: int, shipment: models.ShipmentUpdate) -> models.ShipmentPublic:
|
|
||||||
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
shipment: models.ShipmentUpdate) -> models.ShipmentPublic:
|
||||||
if shipment is None:
|
if shipment is None:
|
||||||
raise exceptions.ShipmentCreateError(messages.Messages.invalid_input('shipment', 'input cannot be None'))
|
raise exceptions.ShipmentCreateError(
|
||||||
statement = select(models.Shipment).where(models.Shipment.id == id)
|
messages.Messages.invalid_input(
|
||||||
|
'shipment', 'input cannot be None'))
|
||||||
|
statement = select(models.Shipment).where(models.Shipment.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_shipment = result.first()
|
new_shipment = result.first()
|
||||||
if not new_shipment:
|
if not new_shipment:
|
||||||
raise exceptions.ShipmentNotFoundError(messages.Messages.not_found('shipment'))
|
raise exceptions.ShipmentNotFoundError(
|
||||||
|
messages.Messages.not_found('shipment'))
|
||||||
|
|
||||||
products_to_add = session.exec(select(models.Product).where(models.Product.id.in_(shipment.product_ids))).all()
|
products_to_add = session.exec(
|
||||||
|
select(
|
||||||
|
models.Product
|
||||||
|
).where(
|
||||||
|
models.Product.id.in_(
|
||||||
|
shipment.product_ids
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
new_shipment.products.clear()
|
new_shipment.products.clear()
|
||||||
for add in products_to_add:
|
for add in products_to_add:
|
||||||
new_shipment.products.append(add)
|
new_shipment.products.append(add)
|
||||||
|
|
||||||
shipment_updates = shipment.model_dump(exclude_unset=True, exclude={"product_ids"})
|
shipment_updates = shipment.model_dump(
|
||||||
|
exclude_unset=True, exclude={"product_ids"}
|
||||||
|
)
|
||||||
for key, value in shipment_updates.items():
|
for key, value in shipment_updates.items():
|
||||||
setattr(new_shipment, key, value)
|
setattr(new_shipment, key, value)
|
||||||
|
|
||||||
@@ -61,14 +113,48 @@ def update_one(session: Session, id: int, shipment: models.ShipmentUpdate) -> mo
|
|||||||
session.refresh(new_shipment)
|
session.refresh(new_shipment)
|
||||||
return new_shipment
|
return new_shipment
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.ShipmentPublic:
|
|
||||||
statement = select(models.Shipment).where(models.Shipment.id == id)
|
def delete_one(session: Session, _id: int) -> models.ShipmentPublic:
|
||||||
|
statement = select(models.Shipment).where(models.Shipment.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
shipment = result.first()
|
shipment = result.first()
|
||||||
if not shipment:
|
if not shipment:
|
||||||
raise exceptions.ShipmentNotFoundError(messages.Messages.not_found('shipment'))
|
raise exceptions.ShipmentNotFoundError(
|
||||||
|
messages.Messages.not_found('shipment'))
|
||||||
|
|
||||||
result = models.ShipmentPublic.model_validate(shipment)
|
result = models.ShipmentPublic.model_validate(shipment)
|
||||||
session.delete(shipment)
|
session.delete(shipment)
|
||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed(
|
||||||
|
session: Session,
|
||||||
|
user: models.User,
|
||||||
|
_id: int = None,
|
||||||
|
shipment: models.ShipmentCreate = None,
|
||||||
|
):
|
||||||
|
if not _id and not shipment:
|
||||||
|
return False
|
||||||
|
if not _id:
|
||||||
|
statement = (
|
||||||
|
select(models.Form)
|
||||||
|
.where(models.Form.id == shipment.form_id)
|
||||||
|
)
|
||||||
|
form = session.exec(statement).first()
|
||||||
|
return form.productor.type in [r.name for r in user.roles]
|
||||||
|
statement = (
|
||||||
|
select(models.Shipment)
|
||||||
|
.join(
|
||||||
|
models.Form,
|
||||||
|
models.Shipment.form_id == models.Form.id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
models.Productor,
|
||||||
|
models.Form.productor_id == models.Productor.id
|
||||||
|
)
|
||||||
|
.where(models.Shipment.id == _id)
|
||||||
|
.where(models.Productor.type.in_([r.name for r in user.roles]))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return len(session.exec(statement).all()) > 0
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
import src.shipments.service as service
|
|
||||||
import src.shipments.exceptions as exceptions
|
import src.shipments.exceptions as exceptions
|
||||||
|
import src.shipments.service as service
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
|
||||||
router = APIRouter(prefix='/shipments')
|
router = APIRouter(prefix='/shipments')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.ShipmentPublic], )
|
@router.get('', response_model=list[models.ShipmentPublic], )
|
||||||
def get_shipments(
|
def get_shipments(
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
@@ -25,50 +25,95 @@ def get_shipments(
|
|||||||
forms,
|
forms,
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.ShipmentPublic)
|
|
||||||
|
@router.get(
|
||||||
|
'/{_id}/preview-delete',
|
||||||
|
response_model=list[models.DeleteDependency]
|
||||||
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('shipment', 'delete')
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{_id}', response_model=models.ShipmentPublic)
|
||||||
def get_shipment(
|
def get_shipment(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
result = service.get_one(session, id)
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('shipment', 'get')
|
||||||
|
)
|
||||||
|
result = service.get_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('shipment'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('shipment')
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.ShipmentPublic)
|
@router.post('', response_model=models.ShipmentPublic)
|
||||||
def create_shipment(
|
def create_shipment(
|
||||||
shipment: models.ShipmentCreate,
|
shipment: models.ShipmentCreate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, shipment=shipment):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('shipment', 'create')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.create_one(session, shipment)
|
result = service.create_one(session, shipment)
|
||||||
except exceptions.ShipmentCreateError as error:
|
except exceptions.ShipmentCreateError as error:
|
||||||
raise HTTPException(status_code=400, detail=str(error))
|
raise HTTPException(status_code=400, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.ShipmentPublic)
|
|
||||||
|
@router.put('/{_id}', response_model=models.ShipmentPublic)
|
||||||
def update_shipment(
|
def update_shipment(
|
||||||
id: int,
|
_id: int,
|
||||||
shipment: models.ShipmentUpdate,
|
shipment: models.ShipmentUpdate,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('shipment', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.update_one(session, id, shipment)
|
result = service.update_one(session, _id, shipment)
|
||||||
except exceptions.ShipmentNotFoundError as error:
|
except exceptions.ShipmentNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.ShipmentPublic)
|
|
||||||
|
@router.delete('/{_id}', response_model=models.ShipmentPublic)
|
||||||
def delete_shipment(
|
def delete_shipment(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(session, user, _id=_id):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('shipment', 'delete')
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, _id)
|
||||||
except exceptions.ShipmentNotFoundError as error:
|
except exceptions.ShipmentNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=str(error))
|
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def get_all(session: Session) -> list[models.TemplatePublic]:
|
def get_all(session: Session) -> list[models.TemplatePublic]:
|
||||||
statement = select(models.Template)
|
statement = select(models.Template)
|
||||||
return session.exec(statement.order_by(models.Template.name)).all()
|
return session.exec(statement.order_by(models.Template.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_one(session: Session, template_id: int) -> models.TemplatePublic:
|
def get_one(session: Session, template_id: int) -> models.TemplatePublic:
|
||||||
return session.get(models.Template, template_id)
|
return session.get(models.Template, template_id)
|
||||||
|
|
||||||
def create_one(session: Session, template: models.TemplateCreate) -> models.TemplatePublic:
|
|
||||||
|
def create_one(
|
||||||
|
session: Session,
|
||||||
|
template: models.TemplateCreate) -> models.TemplatePublic:
|
||||||
template_create = template.model_dump(exclude_unset=True)
|
template_create = template.model_dump(exclude_unset=True)
|
||||||
new_template = models.Template(**template_create)
|
new_template = models.Template(**template_create)
|
||||||
session.add(new_template)
|
session.add(new_template)
|
||||||
@@ -16,7 +21,11 @@ def create_one(session: Session, template: models.TemplateCreate) -> models.Temp
|
|||||||
session.refresh(new_template)
|
session.refresh(new_template)
|
||||||
return new_template
|
return new_template
|
||||||
|
|
||||||
def update_one(session: Session, id: int, template: models.TemplateUpdate) -> models.TemplatePublic:
|
|
||||||
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
id: int,
|
||||||
|
template: models.TemplateUpdate) -> models.TemplatePublic:
|
||||||
statement = select(models.Template).where(models.Template.id == id)
|
statement = select(models.Template).where(models.Template.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_template = result.first()
|
new_template = result.first()
|
||||||
@@ -30,6 +39,7 @@ def update_one(session: Session, id: int, template: models.TemplateUpdate) -> mo
|
|||||||
session.refresh(new_template)
|
session.refresh(new_template)
|
||||||
return new_template
|
return new_template
|
||||||
|
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.TemplatePublic:
|
def delete_one(session: Session, id: int) -> models.TemplatePublic:
|
||||||
statement = select(models.Template).where(models.Template.id == id)
|
statement = select(models.Template).where(models.Template.id == id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends
|
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
import src.templates.service as service
|
import src.templates.service as service
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
from src.auth.auth import get_current_user
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
|
||||||
router = APIRouter(prefix='/templates')
|
router = APIRouter(prefix='/templates')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.TemplatePublic])
|
@router.get('', response_model=list[models.TemplatePublic])
|
||||||
def get_templates(
|
def get_templates(
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
@@ -15,6 +15,7 @@ def get_templates(
|
|||||||
):
|
):
|
||||||
return service.get_all(session)
|
return service.get_all(session)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.TemplatePublic)
|
@router.get('/{id}', response_model=models.TemplatePublic)
|
||||||
def get_template(
|
def get_template(
|
||||||
id: int,
|
id: int,
|
||||||
@@ -23,9 +24,11 @@ def get_template(
|
|||||||
):
|
):
|
||||||
result = service.get_one(session, id)
|
result = service.get_one(session, id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('template'))
|
raise HTTPException(status_code=404,
|
||||||
|
detail=messages.Messages.not_found('template'))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.TemplatePublic)
|
@router.post('', response_model=models.TemplatePublic)
|
||||||
def create_template(
|
def create_template(
|
||||||
template: models.TemplateCreate,
|
template: models.TemplateCreate,
|
||||||
@@ -34,6 +37,7 @@ def create_template(
|
|||||||
):
|
):
|
||||||
return service.create_one(session, template)
|
return service.create_one(session, template)
|
||||||
|
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.TemplatePublic)
|
@router.put('/{id}', response_model=models.TemplatePublic)
|
||||||
def update_template(
|
def update_template(
|
||||||
id: int, template: models.TemplateUpdate,
|
id: int, template: models.TemplateUpdate,
|
||||||
@@ -42,9 +46,11 @@ def update_template(
|
|||||||
):
|
):
|
||||||
result = service.update_one(session, id, template)
|
result = service.update_one(session, id, template)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('template'))
|
raise HTTPException(status_code=404,
|
||||||
|
detail=messages.Messages.not_found('template'))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.TemplatePublic)
|
@router.delete('/{id}', response_model=models.TemplatePublic)
|
||||||
def delete_template(
|
def delete_template(
|
||||||
id: int,
|
id: int,
|
||||||
@@ -53,5 +59,6 @@ def delete_template(
|
|||||||
):
|
):
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('template'))
|
raise HTTPException(status_code=404,
|
||||||
|
detail=messages.Messages.not_found('template'))
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class UserServiceError(Exception):
|
class UserServiceError(Exception):
|
||||||
def __init__(self, message: str):
|
def __init__(self, message: str):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
logging.error('UserService : %s', message)
|
||||||
|
|
||||||
|
|
||||||
class UserNotFoundError(UserServiceError):
|
class UserNotFoundError(UserServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserCreateError(UserServiceError):
|
class UserCreateError(UserServiceError):
|
||||||
def __init__(self, message: str, field: str | None = None):
|
def __init__(self, message: str, field: str | None = None):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from sqlmodel import Session, select
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.messages as messages
|
|
||||||
|
|
||||||
import src.users.exceptions as exceptions
|
import src.users.exceptions as exceptions
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from src import messages, models
|
||||||
|
|
||||||
|
|
||||||
def get_all(
|
def get_all(
|
||||||
session: Session,
|
session: Session,
|
||||||
@@ -17,11 +15,15 @@ def get_all(
|
|||||||
statement = statement.where(models.User.email.in_(emails))
|
statement = statement.where(models.User.email.in_(emails))
|
||||||
return session.exec(statement.order_by(models.User.name)).all()
|
return session.exec(statement.order_by(models.User.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_one(session: Session, user_id: int) -> models.UserPublic:
|
def get_one(session: Session, user_id: int) -> models.UserPublic:
|
||||||
return session.get(models.User, user_id)
|
return session.get(models.User, user_id)
|
||||||
|
|
||||||
def get_or_create_roles(session: Session, role_names: list[str]) -> list[models.ContractType]:
|
|
||||||
statement = select(models.ContractType).where(models.ContractType.name.in_(role_names))
|
def get_or_create_roles(session: Session,
|
||||||
|
role_names: list[str]) -> list[models.ContractType]:
|
||||||
|
statement = select(models.ContractType).where(
|
||||||
|
models.ContractType.name.in_(role_names))
|
||||||
existing = session.exec(statement).all()
|
existing = session.exec(statement).all()
|
||||||
existing_roles = {role.name for role in existing}
|
existing_roles = {role.name for role in existing}
|
||||||
missing_role = set(role_names) - existing_roles
|
missing_role = set(role_names) - existing_roles
|
||||||
@@ -37,24 +39,36 @@ def get_or_create_roles(session: Session, role_names: list[str]) -> list[models.
|
|||||||
session.refresh(role)
|
session.refresh(role)
|
||||||
return existing + new_roles
|
return existing + new_roles
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_user(session: Session, user_create: models.UserCreate):
|
def get_or_create_user(session: Session, user_create: models.UserCreate):
|
||||||
statement = select(models.User).where(models.User.email == user_create.email)
|
statement = select(
|
||||||
|
models.User).where(
|
||||||
|
models.User.email == user_create.email)
|
||||||
user = session.exec(statement).first()
|
user = session.exec(statement).first()
|
||||||
if user:
|
if user:
|
||||||
user_role_names = [r.name for r in user.roles]
|
user_role_names = [r.name for r in user.roles]
|
||||||
if user_role_names != user_create.role_names or user.name != user_create.name:
|
if (user_role_names != user_create.role_names or
|
||||||
|
user.name != user_create.name):
|
||||||
user = update_one(session, user.id, user_create)
|
user = update_one(session, user.id, user_create)
|
||||||
return user
|
return user
|
||||||
user = create_one(session, user_create)
|
user = create_one(session, user_create)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def get_roles(session: Session):
|
def get_roles(session: Session):
|
||||||
statement = select(models.ContractType)
|
statement = (
|
||||||
|
select(models.ContractType)
|
||||||
|
)
|
||||||
return session.exec(statement.order_by(models.ContractType.name)).all()
|
return session.exec(statement.order_by(models.ContractType.name)).all()
|
||||||
|
|
||||||
|
|
||||||
def create_one(session: Session, user: models.UserCreate) -> models.UserPublic:
|
def create_one(session: Session, user: models.UserCreate) -> models.UserPublic:
|
||||||
if user is None:
|
if user is None:
|
||||||
raise exceptions.UserCreateError(messages.Messages.invalid_input('user', 'input cannot be None'))
|
raise exceptions.UserCreateError(
|
||||||
|
messages.Messages.invalid_input(
|
||||||
|
'user', 'input cannot be None'
|
||||||
|
)
|
||||||
|
)
|
||||||
new_user = models.User(
|
new_user = models.User(
|
||||||
name=user.name,
|
name=user.name,
|
||||||
email=user.email
|
email=user.email
|
||||||
@@ -68,14 +82,22 @@ def create_one(session: Session, user: models.UserCreate) -> models.UserPublic:
|
|||||||
session.refresh(new_user)
|
session.refresh(new_user)
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
def update_one(session: Session, id: int, user: models.UserCreate) -> models.UserPublic:
|
|
||||||
|
def update_one(
|
||||||
|
session: Session,
|
||||||
|
_id: int,
|
||||||
|
user: models.UserCreate) -> models.UserPublic:
|
||||||
if user is None:
|
if user is None:
|
||||||
raise exceptions.UserCreateError(messages.s.invalid_input('user', 'input cannot be None'))
|
raise exceptions.UserCreateError(
|
||||||
statement = select(models.User).where(models.User.id == id)
|
messages.Messages.invalid_input(
|
||||||
|
'user', 'input cannot be None'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
statement = select(models.User).where(models.User.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
new_user = result.first()
|
new_user = result.first()
|
||||||
if not new_user:
|
if not new_user:
|
||||||
raise exceptions.UserNotFoundError(f'User {id} not found')
|
raise exceptions.UserNotFoundError(f'User {_id} not found')
|
||||||
new_user.email = user.email
|
new_user.email = user.email
|
||||||
new_user.name = user.name
|
new_user.name = user.name
|
||||||
|
|
||||||
@@ -86,13 +108,20 @@ def update_one(session: Session, id: int, user: models.UserCreate) -> models.Use
|
|||||||
session.refresh(new_user)
|
session.refresh(new_user)
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
def delete_one(session: Session, id: int) -> models.UserPublic:
|
|
||||||
statement = select(models.User).where(models.User.id == id)
|
def delete_one(session: Session, _id: int) -> models.UserPublic:
|
||||||
|
statement = select(models.User).where(models.User.id == _id)
|
||||||
result = session.exec(statement)
|
result = session.exec(statement)
|
||||||
user = result.first()
|
user = result.first()
|
||||||
if not user:
|
if not user:
|
||||||
raise exceptions.UserNotFoundError(f'User {id} not found')
|
raise exceptions.UserNotFoundError(f'User {_id} not found')
|
||||||
result = models.UserPublic.model_validate(user)
|
result = models.UserPublic.model_validate(user)
|
||||||
session.delete(user)
|
session.delete(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed(
|
||||||
|
logged_user: models.User,
|
||||||
|
):
|
||||||
|
return len(logged_user.roles) >= 5
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
||||||
import src.messages as messages
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from sqlmodel import Session
|
|
||||||
import src.users.service as service
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import src.users.exceptions as exceptions
|
import src.users.exceptions as exceptions
|
||||||
|
import src.users.service as service
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import messages, models
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
|
||||||
router = APIRouter(prefix='/users')
|
router = APIRouter(prefix='/users')
|
||||||
|
|
||||||
|
|
||||||
@router.get('', response_model=list[models.UserPublic])
|
@router.get('', response_model=list[models.UserPublic])
|
||||||
def get_users(
|
def get_users(
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user),
|
_: models.User = Depends(get_current_user),
|
||||||
names: list[str] = Query([]),
|
names: list[str] = Query([]),
|
||||||
emails: list[str] = Query([]),
|
emails: list[str] = Query([]),
|
||||||
):
|
):
|
||||||
@@ -22,57 +22,115 @@ def get_users(
|
|||||||
emails,
|
emails,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get('/roles', response_model=list[models.ContractType])
|
@router.get('/roles', response_model=list[models.ContractType])
|
||||||
def get_roles(
|
def get_roles(
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('roles', 'get all')
|
||||||
|
)
|
||||||
return service.get_roles(session)
|
return service.get_roles(session)
|
||||||
|
|
||||||
@router.get('/{id}', response_model=models.UserPublic)
|
|
||||||
def get_users(
|
@router.get(
|
||||||
id: int,
|
'/{_id}/preview-delete',
|
||||||
|
response_model=list[models.DeleteDependency]
|
||||||
|
)
|
||||||
|
async def preview_delete(
|
||||||
|
_id: int,
|
||||||
|
user: models.User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
if not service.is_allowed(user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('user', 'delete')
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/{_id}', response_model=models.UserPublic)
|
||||||
|
def get_user(
|
||||||
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
result = service.get_one(session, id)
|
if not service.is_allowed(user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('user', 'get')
|
||||||
|
)
|
||||||
|
result = service.get_one(session, _id)
|
||||||
if result is None:
|
if result is None:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('user'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('user')
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post('', response_model=models.UserPublic)
|
@router.post('', response_model=models.UserPublic)
|
||||||
def create_user(
|
def create_user(
|
||||||
user: models.UserCreate,
|
user: models.UserCreate,
|
||||||
logged_user: models.User = Depends(get_current_user),
|
logged_user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(logged_user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('user', 'create')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
user = service.create_one(session, user)
|
user = service.create_one(session, user)
|
||||||
except exceptions.UserCreateError as error:
|
except exceptions.UserCreateError as error:
|
||||||
raise HTTPException(status_code=400, detail=str(error))
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=str(error)
|
||||||
|
) from error
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@router.put('/{id}', response_model=models.UserPublic)
|
|
||||||
|
@router.put('/{_id}', response_model=models.UserPublic)
|
||||||
def update_user(
|
def update_user(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.UserUpdate,
|
user: models.UserUpdate,
|
||||||
logged_user: models.User = Depends(get_current_user),
|
logged_user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(logged_user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('user', 'update')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.update_one(session, id, user)
|
result = service.update_one(session, _id, user)
|
||||||
except exceptions.UserNotFoundError as error:
|
except exceptions.UserNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('user'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('user')
|
||||||
|
) from error
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@router.delete('/{id}', response_model=models.UserPublic)
|
|
||||||
|
@router.delete('/{_id}', response_model=models.UserPublic)
|
||||||
def delete_user(
|
def delete_user(
|
||||||
id: int,
|
_id: int,
|
||||||
user: models.User = Depends(get_current_user),
|
user: models.User = Depends(get_current_user),
|
||||||
session: Session = Depends(get_session)
|
session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
if not service.is_allowed(user):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403,
|
||||||
|
detail=messages.Messages.not_allowed('user', 'delete')
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
result = service.delete_one(session, id)
|
result = service.delete_one(session, _id)
|
||||||
except exceptions.UserNotFoundError as error:
|
except exceptions.UserNotFoundError as error:
|
||||||
raise HTTPException(status_code=404, detail=messages.Messages.not_found('user'))
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=messages.Messages.not_found('user')
|
||||||
|
) from error
|
||||||
return result
|
return result
|
||||||
|
|||||||
BIN
backend/test.pdf
BIN
backend/test.pdf
Binary file not shown.
@@ -1,13 +1,14 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from sqlmodel import SQLModel, Session, create_engine
|
|
||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
|
from sqlmodel import Session, SQLModel, create_engine
|
||||||
|
from src import models
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.database import get_session
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
from .fixtures import *
|
from .fixtures import *
|
||||||
from src.main import app
|
|
||||||
import src.models as models
|
|
||||||
from src.database import get_session
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_session(mocker):
|
def mock_session(mocker):
|
||||||
@@ -20,6 +21,7 @@ def mock_session(mocker):
|
|||||||
yield session
|
yield session
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_user():
|
def mock_user():
|
||||||
user = models.User(id=1, name='test user', email='test@user.com')
|
user = models.User(id=1, name='test user', email='test@user.com')
|
||||||
@@ -31,10 +33,12 @@ def mock_user():
|
|||||||
yield user
|
yield user
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
def client():
|
||||||
return TestClient(app)
|
return TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name='session')
|
@pytest.fixture(name='session')
|
||||||
def session_fixture():
|
def session_fixture():
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
|
|||||||
3
backend/tests/factories/__init__.py
Normal file
3
backend/tests/factories/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026-present Julien Aldon <julien.aldon@wanadoo.fr>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import src.models as models
|
|
||||||
import tests.factories.contracts as contract_factory
|
import tests.factories.contracts as contract_factory
|
||||||
import tests.factories.products as product_factory
|
import tests.factories.products as product_factory
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def contract_product_factory(**kwargs):
|
def contract_product_factory(**kwargs):
|
||||||
contract = contract_factory.contract_factory(id=1)
|
contract = contract_factory.contract_factory(id=1)
|
||||||
product = product_factory.product_public_factory(id=1, type=models.ProductType.RECCURENT)
|
product = product_factory.product_public_factory(
|
||||||
|
id=1, type=models.ProductType.RECCURENT)
|
||||||
data = dict(
|
data = dict(
|
||||||
product_id=1,
|
product_id=1,
|
||||||
shipment_id=1,
|
shipment_id=1,
|
||||||
@@ -16,6 +18,7 @@ def contract_product_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractProduct(**data)
|
return models.ContractProduct(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_product_public_factory(**kwargs):
|
def contract_product_public_factory(**kwargs):
|
||||||
contract = contract_factory.contract_factory(id=1)
|
contract = contract_factory.contract_factory(id=1)
|
||||||
product = product_factory.product_public_factory(id=1)
|
product = product_factory.product_public_factory(id=1)
|
||||||
@@ -31,6 +34,7 @@ def contract_product_public_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractProductPublic(**data)
|
return models.ContractProductPublic(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_product_create_factory(**kwargs):
|
def contract_product_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
product_id=1,
|
product_id=1,
|
||||||
@@ -40,6 +44,7 @@ def contract_product_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractProductCreate(**data)
|
return models.ContractProductCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_product_update_factory(**kwargs):
|
def contract_product_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
product_id=1,
|
product_id=1,
|
||||||
@@ -49,6 +54,7 @@ def contract_product_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractProductUpdate(**data)
|
return models.ContractProductUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_product_body_factory(**kwargs):
|
def contract_product_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
product_id=1,
|
product_id=1,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
from .forms import form_factory
|
from .forms import form_factory
|
||||||
|
|
||||||
|
|
||||||
def contract_factory(**kwargs):
|
def contract_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -17,6 +19,7 @@ def contract_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.Contract(**data)
|
return models.Contract(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_public_factory(**kwargs):
|
def contract_public_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -33,6 +36,7 @@ def contract_public_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractPublic(**data)
|
return models.ContractPublic(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_create_factory(**kwargs):
|
def contract_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
firstname="test",
|
firstname="test",
|
||||||
@@ -48,6 +52,7 @@ def contract_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractCreate(**data)
|
return models.ContractCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_update_factory(**kwargs):
|
def contract_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
firstname="test",
|
firstname="test",
|
||||||
@@ -60,6 +65,7 @@ def contract_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ContractUpdate(**data)
|
return models.ContractUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def contract_body_factory(**kwargs):
|
def contract_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
firstname="test",
|
firstname="test",
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import src.models as models
|
|
||||||
from .productors import productor_public_factory
|
|
||||||
from .shipments import shipment_public_factory
|
|
||||||
from .users import user_factory
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
from .productors import productor_public_factory
|
||||||
|
from .users import user_factory
|
||||||
|
|
||||||
|
|
||||||
def form_factory(**kwargs):
|
def form_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -37,6 +39,7 @@ def form_body_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def form_create_factory(**kwargs):
|
def form_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name='form 1',
|
name='form 1',
|
||||||
@@ -51,6 +54,7 @@ def form_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.FormCreate(**data)
|
return models.FormCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def form_update_factory(**kwargs):
|
def form_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name='form 1',
|
name='form 1',
|
||||||
@@ -65,6 +69,7 @@ def form_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.FormUpdate(**data)
|
return models.FormUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def form_public_factory(form=None, shipments=[], **kwargs):
|
def form_public_factory(form=None, shipments=[], **kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def productor_factory(**kwargs):
|
def productor_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
@@ -10,6 +11,7 @@ def productor_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.Productor(**data)
|
return models.Productor(**data)
|
||||||
|
|
||||||
|
|
||||||
def productor_public_factory(**kwargs):
|
def productor_public_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -22,6 +24,7 @@ def productor_public_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ProductorPublic(**data)
|
return models.ProductorPublic(**data)
|
||||||
|
|
||||||
|
|
||||||
def productor_create_factory(**kwargs):
|
def productor_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -34,6 +37,7 @@ def productor_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ProductorCreate(**data)
|
return models.ProductorCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def productor_update_factory(**kwargs):
|
def productor_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -46,6 +50,7 @@ def productor_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ProductorUpdate(**data)
|
return models.ProductorUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def productor_body_factory(**kwargs):
|
def productor_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
from .productors import productor_factory
|
from .productors import productor_factory
|
||||||
from .shipments import shipment_factory
|
|
||||||
|
|
||||||
def product_body_factory(**kwargs):
|
def product_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
@@ -16,6 +17,7 @@ def product_body_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def product_create_factory(**kwargs):
|
def product_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name='product test 1',
|
name='product test 1',
|
||||||
@@ -30,6 +32,7 @@ def product_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ProductCreate(**data)
|
return models.ProductCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def product_update_factory(**kwargs):
|
def product_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name='product test 1',
|
name='product test 1',
|
||||||
@@ -44,6 +47,7 @@ def product_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ProductUpdate(**data)
|
return models.ProductUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def product_public_factory(productor=None, shipments=[], **kwargs):
|
def product_public_factory(productor=None, shipments=[], **kwargs):
|
||||||
if productor is None:
|
if productor is None:
|
||||||
productor = productor_factory()
|
productor = productor_factory()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import src.models as models
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def shipment_factory(**kwargs):
|
def shipment_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -11,6 +13,7 @@ def shipment_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.Shipment(**data)
|
return models.Shipment(**data)
|
||||||
|
|
||||||
|
|
||||||
def shipment_public_factory(**kwargs):
|
def shipment_public_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -23,6 +26,7 @@ def shipment_public_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ShipmentPublic(**data)
|
return models.ShipmentPublic(**data)
|
||||||
|
|
||||||
|
|
||||||
def shipment_create_factory(**kwargs):
|
def shipment_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test shipment",
|
name="test shipment",
|
||||||
@@ -33,6 +37,7 @@ def shipment_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ShipmentCreate(**data)
|
return models.ShipmentCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def shipment_update_factory(**kwargs):
|
def shipment_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test shipment",
|
name="test shipment",
|
||||||
@@ -43,6 +48,7 @@ def shipment_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.ShipmentUpdate(**data)
|
return models.ShipmentUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def shipment_body_factory(**kwargs):
|
def shipment_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test shipment",
|
name="test shipment",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import src.models as models
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
def user_factory(**kwargs):
|
def user_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
@@ -10,6 +11,7 @@ def user_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.User(**data)
|
return models.User(**data)
|
||||||
|
|
||||||
|
|
||||||
def user_public_factory(**kwargs):
|
def user_public_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -20,6 +22,7 @@ def user_public_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.UserPublic(**data)
|
return models.UserPublic(**data)
|
||||||
|
|
||||||
|
|
||||||
def user_create_factory(**kwargs):
|
def user_create_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test user",
|
name="test user",
|
||||||
@@ -29,6 +32,7 @@ def user_create_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.UserCreate(**data)
|
return models.UserCreate(**data)
|
||||||
|
|
||||||
|
|
||||||
def user_update_factory(**kwargs):
|
def user_update_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test user",
|
name="test user",
|
||||||
@@ -38,6 +42,7 @@ def user_update_factory(**kwargs):
|
|||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
return models.UserUpdate(**data)
|
return models.UserUpdate(**data)
|
||||||
|
|
||||||
|
|
||||||
def user_body_factory(**kwargs):
|
def user_body_factory(**kwargs):
|
||||||
data = dict(
|
data = dict(
|
||||||
name="test user",
|
name="test user",
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
import pytest
|
|
||||||
import datetime
|
import datetime
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
import pytest
|
||||||
import src.forms.service as forms_service
|
import src.forms.service as forms_service
|
||||||
import src.shipments.service as shipments_service
|
|
||||||
import src.productors.service as productors_service
|
import src.productors.service as productors_service
|
||||||
import src.products.service as products_service
|
import src.products.service as products_service
|
||||||
|
import src.shipments.service as shipments_service
|
||||||
import src.users.service as users_service
|
import src.users.service as users_service
|
||||||
import tests.factories.forms as forms_factory
|
import tests.factories.forms as forms_factory
|
||||||
import tests.factories.shipments as shipments_factory
|
|
||||||
import tests.factories.productors as productors_factory
|
import tests.factories.productors as productors_factory
|
||||||
import tests.factories.products as products_factory
|
import tests.factories.products as products_factory
|
||||||
|
import tests.factories.shipments as shipments_factory
|
||||||
import tests.factories.users as users_factory
|
import tests.factories.users as users_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def productor(session: Session) -> models.ProductorPublic:
|
def productor(session: Session) -> models.ProductorPublic:
|
||||||
productor = productors_service.create_one(
|
result = productors_service.create_one(
|
||||||
session,
|
session,
|
||||||
productors_factory.productor_create_factory(
|
productors_factory.productor_create_factory(
|
||||||
name='test productor',
|
name='test productor',
|
||||||
type='Légumineuses',
|
type='Légumineuses',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return productor
|
return result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def productors(session: Session) -> models.ProductorPublic:
|
def productors(session: Session) -> models.ProductorPublic:
|
||||||
productors = [
|
result = [
|
||||||
productors_service.create_one(
|
productors_service.create_one(
|
||||||
session,
|
session,
|
||||||
productors_factory.productor_create_factory(
|
productors_factory.productor_create_factory(
|
||||||
@@ -44,11 +45,15 @@ def productors(session: Session) -> models.ProductorPublic:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return productors
|
return result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def products(session: Session, productor: models.ProductorPublic) -> list[models.ProductPublic]:
|
def products(
|
||||||
products = [
|
session: Session,
|
||||||
|
productor: models.ProductorPublic
|
||||||
|
) -> list[models.ProductPublic]:
|
||||||
|
result = [
|
||||||
products_service.create_one(
|
products_service.create_one(
|
||||||
session,
|
session,
|
||||||
products_factory.product_create_factory(
|
products_factory.product_create_factory(
|
||||||
@@ -66,7 +71,8 @@ def products(session: Session, productor: models.ProductorPublic) -> list[models
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
return products
|
return result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user(session: Session) -> models.UserPublic:
|
def user(session: Session) -> models.UserPublic:
|
||||||
@@ -80,39 +86,40 @@ def user(session: Session) -> models.UserPublic:
|
|||||||
)
|
)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def users(session: Session) -> list[models.UserPublic]:
|
def users(session: Session) -> list[models.UserPublic]:
|
||||||
users = [
|
result = [
|
||||||
users_service.create_one(
|
users_service.create_one(
|
||||||
session,
|
session,
|
||||||
users_factory.user_create_factory(
|
users_factory.user_create_factory(
|
||||||
name='test user 1 (admin)',
|
name='test user 1 (admin)',
|
||||||
email='test1@test.com',
|
email='test1@test.com',
|
||||||
role_names=['Légumineuses', 'Légumes', 'Oeufs', 'Porc-Agneau', 'Vin', 'Fruits']
|
role_names=[
|
||||||
)
|
'Légumineuses',
|
||||||
),
|
'Légumes',
|
||||||
|
'Oeufs',
|
||||||
|
'Porc-Agneau',
|
||||||
|
'Vin',
|
||||||
|
'Fruits'])),
|
||||||
users_service.create_one(
|
users_service.create_one(
|
||||||
session,
|
session,
|
||||||
users_factory.user_create_factory(
|
users_factory.user_create_factory(
|
||||||
name='test user 2',
|
name='test user 2',
|
||||||
email='test2@test.com',
|
email='test2@test.com',
|
||||||
role_names=['Légumineuses']
|
role_names=['Légumineuses'])),
|
||||||
)
|
|
||||||
),
|
|
||||||
users_service.create_one(
|
users_service.create_one(
|
||||||
session,
|
session,
|
||||||
users_factory.user_create_factory(
|
users_factory.user_create_factory(
|
||||||
name='test user 3',
|
name='test user 3',
|
||||||
email='test3@test.com',
|
email='test3@test.com',
|
||||||
role_names=['Porc-Agneau']
|
role_names=['Porc-Agneau']))]
|
||||||
)
|
return result
|
||||||
)
|
|
||||||
]
|
|
||||||
return users
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def referer(session: Session) -> models.UserPublic:
|
def referer(session: Session) -> models.UserPublic:
|
||||||
referer = users_service.create_one(
|
result = users_service.create_one(
|
||||||
session,
|
session,
|
||||||
users_factory.user_create_factory(
|
users_factory.user_create_factory(
|
||||||
name='test referer',
|
name='test referer',
|
||||||
@@ -120,11 +127,16 @@ def referer(session: Session) -> models.UserPublic:
|
|||||||
role_names=['Légumineuses'],
|
role_names=['Légumineuses'],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return referer
|
return result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def shipments(session: Session, forms: list[models.FormPublic], products: list[models.ProductPublic]):
|
def shipments(
|
||||||
shipments = [
|
session: Session,
|
||||||
|
forms: list[models.FormPublic],
|
||||||
|
products: list[models.ProductPublic]
|
||||||
|
):
|
||||||
|
result = [
|
||||||
shipments_service.create_one(
|
shipments_service.create_one(
|
||||||
session,
|
session,
|
||||||
shipments_factory.shipment_create_factory(
|
shipments_factory.shipment_create_factory(
|
||||||
@@ -144,7 +156,8 @@ def shipments(session: Session, forms: list[models.FormPublic], products: list[m
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
return shipments
|
return result
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def forms(
|
def forms(
|
||||||
@@ -152,7 +165,7 @@ def forms(
|
|||||||
productor: models.ProductorPublic,
|
productor: models.ProductorPublic,
|
||||||
referer: models.UserPublic
|
referer: models.UserPublic
|
||||||
) -> list[models.FormPublic]:
|
) -> list[models.FormPublic]:
|
||||||
forms = [
|
result = [
|
||||||
forms_service.create_one(
|
forms_service.create_one(
|
||||||
session,
|
session,
|
||||||
forms_factory.form_create_factory(
|
forms_factory.form_create_factory(
|
||||||
@@ -172,5 +185,4 @@ def forms(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return forms
|
return result
|
||||||
|
|
||||||
|
|||||||
3
backend/tests/routers/__init__.py
Normal file
3
backend/tests/routers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026-present Julien Aldon <julien.aldon@wanadoo.fr>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import src.contracts.service as service
|
import src.contracts.service as service
|
||||||
import src.models as models
|
|
||||||
from src.main import app
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import tests.factories.contracts as contract_factory
|
import tests.factories.contracts as contract_factory
|
||||||
import tests.factories.forms as form_factory
|
|
||||||
import tests.factories.contract_products as contract_products_factory
|
|
||||||
|
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
class TestContracts:
|
class TestContracts:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
contract_factory.contract_public_factory(id=1),
|
contract_factory.contract_public_factory(id=1),
|
||||||
contract_factory.contract_public_factory(id=2),
|
contract_factory.contract_public_factory(id=2),
|
||||||
@@ -31,7 +34,14 @@ class TestContracts:
|
|||||||
mock_user,
|
mock_user,
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
contract_factory.contract_public_factory(id=2),
|
contract_factory.contract_public_factory(id=2),
|
||||||
]
|
]
|
||||||
@@ -52,7 +62,11 @@ class TestContracts:
|
|||||||
['form test'],
|
['form test'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -66,7 +80,13 @@ class TestContracts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = contract_factory.contract_public_factory(id=2)
|
mock_result = contract_factory.contract_public_factory(id=2)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -88,29 +108,48 @@ class TestContracts:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = None
|
mock_result = None
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_is_allowed = mocker.patch.object(
|
mock_is_allowed = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'is_allowed',
|
'is_allowed',
|
||||||
return_value=True
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/contracts/2')
|
response = client.get('/api/contracts/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -124,42 +163,13 @@ class TestContracts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
contract_body = contract_factory.contract_body_factory(
|
self,
|
||||||
products=[
|
client,
|
||||||
contract_products_factory.contract_product_body_factory(product_id=1),
|
mocker,
|
||||||
contract_products_factory.contract_product_body_factory(product_id=2),
|
mock_session,
|
||||||
contract_products_factory.contract_product_body_factory(product_id=3)
|
mock_user,
|
||||||
],
|
):
|
||||||
cheques=[{'name': '123123', 'value': '100'}]
|
|
||||||
)
|
|
||||||
contract_result = contract_factory.contract_factory(
|
|
||||||
products=[
|
|
||||||
contract_products_factory.contract_product_factory(product_id=1),
|
|
||||||
contract_products_factory.contract_product_factory(product_id=2),
|
|
||||||
contract_products_factory.contract_product_factory(product_id=3)
|
|
||||||
],
|
|
||||||
form=form_factory.form_factory(),
|
|
||||||
cheques=[models.Cheque(name='123123', value='100')]
|
|
||||||
)
|
|
||||||
mock_create_one = mocker.patch.object(
|
|
||||||
service,
|
|
||||||
'create_one',
|
|
||||||
return_value=contract_result
|
|
||||||
)
|
|
||||||
mock_add_contract_file = mocker.patch.object(
|
|
||||||
service,
|
|
||||||
'add_contract_file',
|
|
||||||
return_value=True
|
|
||||||
)
|
|
||||||
mock_generate_html_contract = mocker.patch('src.contracts.generate_contract.generate_html_contract')
|
|
||||||
|
|
||||||
response = client.post('/api/contracts', json=contract_body)
|
|
||||||
assert response.status_code == 200
|
|
||||||
contract_id = 'test_test_test type_hiver-2026'
|
|
||||||
assert response.headers['Content-Disposition'] == f'attachment; filename=contract_{contract_id}.pdf'
|
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
|
||||||
contract_result = contract_factory.contract_public_factory()
|
contract_result = contract_factory.contract_public_factory()
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -167,7 +177,6 @@ class TestContracts:
|
|||||||
'delete_one',
|
'delete_one',
|
||||||
return_value=contract_result
|
return_value=contract_result
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_is_allowed = mocker.patch.object(
|
mock_is_allowed = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'is_allowed',
|
'is_allowed',
|
||||||
@@ -175,15 +184,25 @@ class TestContracts:
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.delete('/api/contracts/2')
|
response = client.delete('/api/contracts/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
contract_result = None
|
contract_result = None
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -191,7 +210,6 @@ class TestContracts:
|
|||||||
'delete_one',
|
'delete_one',
|
||||||
return_value=contract_result
|
return_value=contract_result
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_is_allowed = mocker.patch.object(
|
mock_is_allowed = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'is_allowed',
|
'is_allowed',
|
||||||
@@ -199,18 +217,25 @@ class TestContracts:
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.delete('/api/contracts/2')
|
response = client.delete('/api/contracts/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
contract_body = contract_factory.contract_body_factory()
|
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import src.forms.service as service
|
|
||||||
import src.forms.exceptions as forms_exceptions
|
import src.forms.exceptions as forms_exceptions
|
||||||
import src.models as models
|
import src.forms.service as service
|
||||||
from src.main import app
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import tests.factories.forms as form_factory
|
import tests.factories.forms as form_factory
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
import src.messages as messages
|
from src import messages
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
class TestForms:
|
class TestForms:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
form_factory.form_public_factory(name="test 1", id=1),
|
form_factory.form_public_factory(name="test 1", id=1),
|
||||||
form_factory.form_public_factory(name="test 2", id=2),
|
form_factory.form_public_factory(name="test 2", id=2),
|
||||||
@@ -32,7 +38,14 @@ class TestForms:
|
|||||||
False,
|
False,
|
||||||
mock_user,
|
mock_user,
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
form_factory.form_public_factory(name="test 2", id=2),
|
form_factory.form_public_factory(name="test 2", id=2),
|
||||||
]
|
]
|
||||||
@@ -42,7 +55,8 @@ class TestForms:
|
|||||||
return_value=mock_results
|
return_value=mock_results
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/forms/referents?current_season=true&seasons=hiver-2025&productors=test productor')
|
response = client.get(
|
||||||
|
'/api/forms/referents?current_season=true&seasons=hiver-2025&productors=test productor')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response_data[0]['id'] == 2
|
assert response_data[0]['id'] == 2
|
||||||
@@ -55,7 +69,11 @@ class TestForms:
|
|||||||
mock_user,
|
mock_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -69,7 +87,13 @@ class TestForms:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = form_factory.form_public_factory(name="test 2", id=2)
|
mock_result = form_factory.form_public_factory(name="test 2", id=2)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -77,7 +101,6 @@ class TestForms:
|
|||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/forms/2')
|
response = client.get('/api/forms/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
|
|
||||||
@@ -87,8 +110,14 @@ class TestForms:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
assert mock_user
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
):
|
||||||
mock_result = None
|
mock_result = None
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -96,15 +125,19 @@ class TestForms:
|
|||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
response = client.get('/api/forms/2')
|
response = client.get('/api/forms/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_create_one(
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_body = form_factory.form_body_factory(name='test form create')
|
form_body = form_factory.form_body_factory(name='test form create')
|
||||||
form_create = form_factory.form_create_factory(name='test form create')
|
form_create = form_factory.form_create_factory(name='test form create')
|
||||||
form_result = form_factory.form_public_factory(name='test form create')
|
form_result = form_factory.form_public_factory(name='test form create')
|
||||||
@@ -114,6 +147,11 @@ class TestForms:
|
|||||||
'create_one',
|
'create_one',
|
||||||
return_value=form_result
|
return_value=form_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.post('/api/forms', json=form_body)
|
response = client.post('/api/forms', json=form_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -124,46 +162,95 @@ class TestForms:
|
|||||||
mock_session,
|
mock_session,
|
||||||
form_create
|
form_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
form=form_create
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_one_referer_notfound(self, client, mocker, mock_session, mock_user):
|
def test_create_one_referer_notfound(
|
||||||
form_body = form_factory.form_body_factory(name='test form create', referer_id=12312)
|
self,
|
||||||
form_create = form_factory.form_create_factory(name='test form create', referer_id=12312)
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
form_body = form_factory.form_body_factory(
|
||||||
|
name='test form create', referer_id=12312
|
||||||
|
)
|
||||||
|
form_create = form_factory.form_create_factory(
|
||||||
|
name='test form create', referer_id=12312
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'create_one',
|
'create_one',
|
||||||
side_effect=forms_exceptions.UserNotFoundError(messages.Messages.not_found('referer'))
|
side_effect=forms_exceptions.UserNotFoundError(
|
||||||
|
messages.Messages.not_found('referer')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
response = client.post('/api/forms', json=form_body)
|
||||||
|
assert response.status_code == 404
|
||||||
|
mock.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
form_create
|
||||||
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
form=form_create
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_one_productor_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
form_body = form_factory.form_body_factory(
|
||||||
|
name='test form create', productor_id=1231
|
||||||
|
)
|
||||||
|
form_create = form_factory.form_create_factory(
|
||||||
|
name='test form create', productor_id=1231
|
||||||
|
)
|
||||||
|
|
||||||
|
mock = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'create_one',
|
||||||
|
side_effect=forms_exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.post('/api/forms', json=form_body)
|
response = client.post('/api/forms', json=form_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
form_create
|
form_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
def test_create_one_productor_notfound(self, client, mocker, mock_session, mock_user):
|
|
||||||
form_body = form_factory.form_body_factory(name='test form create', productor_id=1231)
|
|
||||||
form_create = form_factory.form_create_factory(name='test form create', productor_id=1231)
|
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
|
||||||
service,
|
|
||||||
'create_one',
|
|
||||||
side_effect=forms_exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.post('/api/forms', json=form_body)
|
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
|
||||||
mock.assert_called_once_with(
|
|
||||||
mock_session,
|
mock_session,
|
||||||
form_create
|
mock_user,
|
||||||
|
form=form_create
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_create_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
form_body = form_factory.form_body_factory(name='test form create')
|
form_body = form_factory.form_body_factory(name='test form create')
|
||||||
@@ -178,7 +265,13 @@ class TestForms:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_update_one(self, client, mocker, mock_session, mock_user):
|
def test_update_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_body = form_factory.form_body_factory(name='test form update')
|
form_body = form_factory.form_body_factory(name='test form update')
|
||||||
form_update = form_factory.form_update_factory(name='test form update')
|
form_update = form_factory.form_update_factory(name='test form update')
|
||||||
form_result = form_factory.form_public_factory(name='test form update')
|
form_result = form_factory.form_public_factory(name='test form update')
|
||||||
@@ -188,6 +281,11 @@ class TestForms:
|
|||||||
'update_one',
|
'update_one',
|
||||||
return_value=form_result
|
return_value=form_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.put('/api/forms/2', json=form_body)
|
response = client.put('/api/forms/2', json=form_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -199,19 +297,36 @@ class TestForms:
|
|||||||
2,
|
2,
|
||||||
form_update
|
form_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_update_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_body = form_factory.form_body_factory(name='test form update')
|
form_body = form_factory.form_body_factory(name='test form update')
|
||||||
form_update = form_factory.form_update_factory(name='test form update')
|
form_update = form_factory.form_update_factory(name='test form update')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
side_effect=forms_exceptions.FormNotFoundError(messages.Messages.not_found('form'))
|
side_effect=forms_exceptions.FormNotFoundError(
|
||||||
|
messages.Messages.not_found('form')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/forms/2', json=form_body)
|
response = client.put('/api/forms/2', json=form_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -219,19 +334,34 @@ class TestForms:
|
|||||||
2,
|
2,
|
||||||
form_update
|
form_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_referer_notfound(self, client, mocker, mock_session, mock_user):
|
def test_update_one_referer_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_body = form_factory.form_body_factory(name='test form update')
|
form_body = form_factory.form_body_factory(name='test form update')
|
||||||
form_update = form_factory.form_update_factory(name='test form update')
|
form_update = form_factory.form_update_factory(name='test form update')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
|
service, 'update_one', side_effect=forms_exceptions.UserNotFoundError(
|
||||||
|
messages.Messages.not_found('referer')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'is_allowed',
|
||||||
side_effect=forms_exceptions.UserNotFoundError(messages.Messages.not_found('referer'))
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/forms/2', json=form_body)
|
response = client.put('/api/forms/2', json=form_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -239,19 +369,36 @@ class TestForms:
|
|||||||
2,
|
2,
|
||||||
form_update
|
form_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_productor_notfound(self, client, mocker, mock_session, mock_user):
|
def test_update_one_productor_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_body = form_factory.form_body_factory(name='test form update')
|
form_body = form_factory.form_body_factory(name='test form update')
|
||||||
form_update = form_factory.form_update_factory(name='test form update')
|
form_update = form_factory.form_update_factory(name='test form update')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
side_effect=forms_exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
side_effect=forms_exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/forms/2', json=form_body)
|
response = client.put('/api/forms/2', json=form_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -259,8 +406,17 @@ class TestForms:
|
|||||||
2,
|
2,
|
||||||
form_update
|
form_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_update_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
form_body = form_factory.form_body_factory(name='test form update')
|
form_body = form_factory.form_body_factory(name='test form update')
|
||||||
@@ -275,7 +431,13 @@ class TestForms:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
form_result = form_factory.form_public_factory(name='test form delete')
|
form_result = form_factory.form_public_factory(name='test form delete')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -283,6 +445,11 @@ class TestForms:
|
|||||||
'delete_one',
|
'delete_one',
|
||||||
return_value=form_result
|
return_value=form_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.delete('/api/forms/2')
|
response = client.delete('/api/forms/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -293,26 +460,49 @@ class TestForms:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
form_result = None
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
side_effect=forms_exceptions.FormNotFoundError(messages.Messages.not_found('form'))
|
side_effect=forms_exceptions.FormNotFoundError(
|
||||||
|
messages.Messages.not_found('form'))
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.delete('/api/forms/2')
|
response = client.delete('/api/forms/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from fastapi.exceptions import HTTPException
|
|
||||||
|
|
||||||
from src.main import app
|
|
||||||
import src.models as models
|
|
||||||
import src.messages as messages
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
|
|
||||||
import src.productors.service as service
|
|
||||||
import src.productors.exceptions as exceptions
|
|
||||||
|
|
||||||
import tests.factories.productors as productor_factory
|
import tests.factories.productors as productor_factory
|
||||||
|
from fastapi.exceptions import HTTPException
|
||||||
|
from src import messages
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
from src.productors import exceptions, service
|
||||||
|
|
||||||
|
|
||||||
class TestProductors:
|
class TestProductors:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
productor_factory.productor_public_factory(name="test 1", id=1),
|
productor_factory.productor_public_factory(name="test 1", id=1),
|
||||||
productor_factory.productor_public_factory(name="test 2", id=2),
|
productor_factory.productor_public_factory(name="test 2", id=2),
|
||||||
@@ -34,7 +36,14 @@ class TestProductors:
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
productor_factory.productor_public_factory(name="test 2", id=2),
|
productor_factory.productor_public_factory(name="test 2", id=2),
|
||||||
]
|
]
|
||||||
@@ -44,7 +53,8 @@ class TestProductors:
|
|||||||
return_value=mock_results
|
return_value=mock_results
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/productors?types=Légumineuses&names=test 2')
|
response = client.get(
|
||||||
|
'/api/productors?types=Légumineuses&names=test 2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response_data[0]['id'] == 2
|
assert response_data[0]['id'] == 2
|
||||||
@@ -56,7 +66,11 @@ class TestProductors:
|
|||||||
['Légumineuses'],
|
['Légumineuses'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -70,8 +84,21 @@ class TestProductors:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
mock_result = productor_factory.productor_public_factory(name="test 2", id=2)
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
mock_result = productor_factory.productor_public_factory(
|
||||||
|
name="test 2", id=2)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -89,22 +116,49 @@ class TestProductors:
|
|||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock_result = None
|
mock_result = None
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
response = client.get('/api/productors/2')
|
response = client.get('/api/productors/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -118,10 +172,25 @@ class TestProductors:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
def test_create_one(
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor create')
|
self,
|
||||||
productor_create = productor_factory.productor_create_factory(name='test productor create')
|
client,
|
||||||
productor_result = productor_factory.productor_public_factory(name='test productor create')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
productor_body = productor_factory.productor_body_factory(
|
||||||
|
name='test productor create')
|
||||||
|
productor_create = productor_factory.productor_create_factory(
|
||||||
|
name='test productor create')
|
||||||
|
productor_result = productor_factory.productor_public_factory(
|
||||||
|
name='test productor create')
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -138,11 +207,21 @@ class TestProductors:
|
|||||||
mock_session,
|
mock_session,
|
||||||
productor_create
|
productor_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
productor=productor_create
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_create_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor create')
|
productor_body = productor_factory.productor_body_factory(
|
||||||
|
name='test productor create')
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -154,10 +233,25 @@ class TestProductors:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_update_one(self, client, mocker, mock_session, mock_user):
|
def test_update_one(
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor update')
|
self,
|
||||||
productor_update = productor_factory.productor_update_factory(name='test productor update')
|
client,
|
||||||
productor_result = productor_factory.productor_public_factory(name='test productor update')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
productor_body = productor_factory.productor_body_factory(
|
||||||
|
name='test productor update')
|
||||||
|
productor_update = productor_factory.productor_update_factory(
|
||||||
|
name='test productor update')
|
||||||
|
productor_result = productor_factory.productor_public_factory(
|
||||||
|
name='test productor update')
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -176,19 +270,41 @@ class TestProductors:
|
|||||||
productor_update
|
productor_update
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_one_notfound(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor update')
|
mock_session,
|
||||||
productor_update = productor_factory.productor_update_factory(name='test productor update')
|
mock_user,
|
||||||
productor_result = None
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
productor_body = productor_factory.productor_body_factory(
|
||||||
|
name='test productor update',
|
||||||
|
)
|
||||||
|
productor_update = productor_factory.productor_update_factory(
|
||||||
|
name='test productor update',
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
side_effect=exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
side_effect=exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/productors/2', json=productor_body)
|
response = client.put('/api/productors/2', json=productor_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -197,10 +313,21 @@ class TestProductors:
|
|||||||
productor_update
|
productor_update
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor update')
|
productor_body = productor_factory.productor_body_factory(
|
||||||
|
name='test productor update')
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -212,8 +339,21 @@ class TestProductors:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
productor_result = productor_factory.productor_public_factory(name='test productor delete')
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
productor_result = productor_factory.productor_public_factory(
|
||||||
|
name='test productor delete')
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -230,18 +370,34 @@ class TestProductors:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
productor_result = None
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
side_effect=exceptions.ProductorNotFoundError(messages.Messages.not_found('productor'))
|
side_effect=exceptions.ProductorNotFoundError(
|
||||||
|
messages.Messages.not_found('productor')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.delete('/api/productors/2')
|
response = client.delete('/api/productors/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -249,11 +405,19 @@ class TestProductors:
|
|||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
productor_body = productor_factory.productor_body_factory(name='test productor delete')
|
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
mock = mocker.patch('src.productors.service.delete_one')
|
mock = mocker.patch('src.productors.service.delete_one')
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import src.products.service as service
|
import src.products.service as service
|
||||||
import src.products.exceptions as exceptions
|
|
||||||
import src.models as models
|
|
||||||
from src.main import app
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import tests.factories.products as product_factory
|
import tests.factories.products as product_factory
|
||||||
|
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
from src.products import exceptions
|
||||||
|
|
||||||
|
|
||||||
class TestProducts:
|
class TestProducts:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
product_factory.product_public_factory(name="test 1", id=1),
|
product_factory.product_public_factory(name="test 1", id=1),
|
||||||
product_factory.product_public_factory(name="test 2", id=2),
|
product_factory.product_public_factory(name="test 2", id=2),
|
||||||
@@ -32,7 +37,14 @@ class TestProducts:
|
|||||||
[],
|
[],
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
product_factory.product_public_factory(name="test 2", id=2),
|
product_factory.product_public_factory(name="test 2", id=2),
|
||||||
]
|
]
|
||||||
@@ -55,7 +67,11 @@ class TestProducts:
|
|||||||
['1'],
|
['1'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -69,8 +85,21 @@ class TestProducts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
mock_result = product_factory.product_public_factory(name="test 2", id=2)
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
mock_result = product_factory.product_public_factory(
|
||||||
|
name="test 2", id=2)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -87,23 +116,47 @@ class TestProducts:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = None
|
mock_result = None
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
response = client.get('/api/products/2')
|
response = client.get('/api/products/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -117,11 +170,25 @@ class TestProducts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
def test_create_one(
|
||||||
product_body = product_factory.product_body_factory(name='test product create')
|
self,
|
||||||
product_create = product_factory.product_create_factory(name='test product create')
|
client,
|
||||||
product_result = product_factory.product_public_factory(name='test product create')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
product_body = product_factory.product_body_factory(
|
||||||
|
name='test product create')
|
||||||
|
product_create = product_factory.product_create_factory(
|
||||||
|
name='test product create')
|
||||||
|
product_result = product_factory.product_public_factory(
|
||||||
|
name='test product create')
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'create_one',
|
'create_one',
|
||||||
@@ -137,11 +204,21 @@ class TestProducts:
|
|||||||
mock_session,
|
mock_session,
|
||||||
product_create
|
product_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
product=product_create
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_create_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
product_body = product_factory.product_body_factory(name='test product create')
|
product_body = product_factory.product_body_factory(
|
||||||
|
name='test product create')
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -153,11 +230,28 @@ class TestProducts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_update_one(self, client, mocker, mock_session, mock_user):
|
def test_update_one(
|
||||||
product_body = product_factory.product_body_factory(name='test product update')
|
self,
|
||||||
product_update = product_factory.product_update_factory(name='test product update')
|
client,
|
||||||
product_result = product_factory.product_public_factory(name='test product update')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
product_body = product_factory.product_body_factory(
|
||||||
|
name='test product update'
|
||||||
|
)
|
||||||
|
product_update = product_factory.product_update_factory(
|
||||||
|
name='test product update'
|
||||||
|
)
|
||||||
|
product_result = product_factory.product_public_factory(
|
||||||
|
name='test product update'
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
@@ -175,11 +269,31 @@ class TestProducts:
|
|||||||
product_update
|
product_update
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_one_notfound(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
product_body = product_factory.product_body_factory(name='test product update')
|
mock_session,
|
||||||
product_update = product_factory.product_update_factory(name='test product update')
|
mock_user,
|
||||||
product_result = None
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
product_body = product_factory.product_body_factory(
|
||||||
|
name='test product update'
|
||||||
|
)
|
||||||
|
product_update = product_factory.product_update_factory(
|
||||||
|
name='test product update'
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
@@ -187,7 +301,6 @@ class TestProducts:
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/products/2', json=product_body)
|
response = client.put('/api/products/2', json=product_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -195,11 +308,21 @@ class TestProducts:
|
|||||||
2,
|
2,
|
||||||
product_update
|
product_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_update_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
product_body = product_factory.product_body_factory(name='test product update')
|
product_body = product_factory.product_body_factory(
|
||||||
|
name='test product update')
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -211,8 +334,15 @@ class TestProducts:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
product_result = product_factory.product_public_factory(name='test product delete')
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
product_result = product_factory.product_public_factory(
|
||||||
|
name='test product delete')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
@@ -220,6 +350,11 @@ class TestProducts:
|
|||||||
return_value=product_result
|
return_value=product_result
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
response = client.delete('/api/products/2')
|
response = client.delete('/api/products/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
|
|
||||||
@@ -229,18 +364,31 @@ class TestProducts:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
product_result = None
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
side_effect=exceptions.ProductNotFoundError('Product not found')
|
side_effect=exceptions.ProductNotFoundError('Product not found')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
response = client.delete('/api/products/2')
|
response = client.delete('/api/products/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -248,11 +396,19 @@ class TestProducts:
|
|||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
product_body = product_factory.product_body_factory(name='test product delete')
|
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
mock = mocker.patch('src.products.service.delete_one')
|
mock = mocker.patch('src.products.service.delete_one')
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import src.shipments.service as service
|
|
||||||
import src.models as models
|
|
||||||
from src.main import app
|
|
||||||
import src.messages as messages
|
|
||||||
import src.shipments.exceptions as exceptions
|
import src.shipments.exceptions as exceptions
|
||||||
from src.auth.auth import get_current_user
|
import src.shipments.service as service
|
||||||
import tests.factories.shipments as shipment_factory
|
import tests.factories.shipments as shipment_factory
|
||||||
|
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
|
from src import messages
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
class TestShipments:
|
class TestShipments:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
shipment_factory.shipment_public_factory(name="test 1", id=1),
|
shipment_factory.shipment_public_factory(name="test 1", id=1),
|
||||||
shipment_factory.shipment_public_factory(name="test 2", id=2),
|
shipment_factory.shipment_public_factory(name="test 2", id=2),
|
||||||
@@ -33,7 +38,14 @@ class TestShipments:
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
shipment_factory.shipment_public_factory(name="test 2", id=2),
|
shipment_factory.shipment_public_factory(name="test 2", id=2),
|
||||||
]
|
]
|
||||||
@@ -43,7 +55,8 @@ class TestShipments:
|
|||||||
return_value=mock_results
|
return_value=mock_results
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/shipments?dates=2025-10-10&names=test 2&forms=contract form 1')
|
response = client.get(
|
||||||
|
'/api/shipments?dates=2025-10-10&names=test 2&forms=contract form 1')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response_data[0]['id'] == 2
|
assert response_data[0]['id'] == 2
|
||||||
@@ -56,7 +69,11 @@ class TestShipments:
|
|||||||
['contract form 1'],
|
['contract form 1'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -70,14 +87,26 @@ class TestShipments:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
mock_result = shipment_factory.shipment_public_factory(name="test 2", id=2)
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
mock_result = shipment_factory.shipment_public_factory(
|
||||||
|
name="test 2", id=2)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.get('/api/shipments/2')
|
response = client.get('/api/shipments/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -88,23 +117,47 @@ class TestShipments:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = None
|
mock_result = None
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
response = client.get('/api/shipments/2')
|
response = client.get('/api/shipments/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -118,16 +171,33 @@ class TestShipments:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
def test_create_one(
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment create')
|
self,
|
||||||
shipment_create = shipment_factory.shipment_create_factory(name='test shipment create')
|
client,
|
||||||
shipment_result = shipment_factory.shipment_public_factory(name='test shipment create')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
shipment_body = shipment_factory.shipment_body_factory(
|
||||||
|
name='test shipment create'
|
||||||
|
)
|
||||||
|
shipment_create = shipment_factory.shipment_create_factory(
|
||||||
|
name='test shipment create'
|
||||||
|
)
|
||||||
|
shipment_result = shipment_factory.shipment_public_factory(
|
||||||
|
name='test shipment create'
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'create_one',
|
'create_one',
|
||||||
return_value=shipment_result
|
return_value=shipment_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.post('/api/shipments', json=shipment_body)
|
response = client.post('/api/shipments', json=shipment_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -138,11 +208,22 @@ class TestShipments:
|
|||||||
mock_session,
|
mock_session,
|
||||||
shipment_create
|
shipment_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
shipment=shipment_create
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_create_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment create')
|
shipment_body = shipment_factory.shipment_body_factory(
|
||||||
|
name='test shipment create'
|
||||||
|
)
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -154,16 +235,33 @@ class TestShipments:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_update_one(self, client, mocker, mock_session, mock_user):
|
def test_update_one(
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment update')
|
self,
|
||||||
shipment_update = shipment_factory.shipment_update_factory(name='test shipment update')
|
client,
|
||||||
shipment_result = shipment_factory.shipment_public_factory(name='test shipment update')
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
shipment_body = shipment_factory.shipment_body_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
shipment_update = shipment_factory.shipment_update_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
shipment_result = shipment_factory.shipment_public_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
return_value=shipment_result
|
return_value=shipment_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.put('/api/shipments/2', json=shipment_body)
|
response = client.put('/api/shipments/2', json=shipment_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -175,19 +273,40 @@ class TestShipments:
|
|||||||
2,
|
2,
|
||||||
shipment_update
|
shipment_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_update_one_notfound(
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment update')
|
self,
|
||||||
shipment_update = shipment_factory.shipment_update_factory(name='test shipment update')
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
shipment_body = shipment_factory.shipment_body_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
shipment_update = shipment_factory.shipment_update_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
side_effect=exceptions.ShipmentNotFoundError(messages.Messages.not_found('shipment'))
|
side_effect=exceptions.ShipmentNotFoundError(
|
||||||
|
messages.Messages.not_found('shipment')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.put('/api/shipments/2', json=shipment_body)
|
response = client.put('/api/shipments/2', json=shipment_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -195,11 +314,22 @@ class TestShipments:
|
|||||||
2,
|
2,
|
||||||
shipment_update
|
shipment_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_update_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment update')
|
shipment_body = shipment_factory.shipment_body_factory(
|
||||||
|
name='test shipment update'
|
||||||
|
)
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
@@ -211,14 +341,27 @@ class TestShipments:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
shipment_result = shipment_factory.shipment_public_factory(name='test shipment delete')
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
|
shipment_result = shipment_factory.shipment_public_factory(
|
||||||
|
name='test shipment delete'
|
||||||
|
)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
return_value=shipment_result
|
return_value=shipment_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.delete('/api/shipments/2')
|
response = client.delete('/api/shipments/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -229,29 +372,52 @@ class TestShipments:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
shipment_result = None
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
side_effect=exceptions.ShipmentNotFoundError(messages.Messages.not_found('shipment'))
|
side_effect=exceptions.ShipmentNotFoundError(
|
||||||
|
messages.Messages.not_found('shipment')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.delete('/api/shipments/2')
|
response = client.delete('/api/shipments/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
_id=2
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
shipment_body = shipment_factory.shipment_body_factory(name='test shipment delete')
|
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import src.users.service as service
|
|
||||||
import src.models as models
|
|
||||||
from src.main import app
|
|
||||||
from src.auth.auth import get_current_user
|
|
||||||
import tests.factories.users as user_factory
|
|
||||||
import src.users.exceptions as exceptions
|
import src.users.exceptions as exceptions
|
||||||
|
import src.users.service as service
|
||||||
|
import tests.factories.users as user_factory
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
|
from src.auth.auth import get_current_user
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
class TestUsers:
|
class TestUsers:
|
||||||
def test_get_all(self, client, mocker, mock_session, mock_user):
|
def test_get_all(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
user_factory.user_public_factory(name="test 1", id=1),
|
user_factory.user_public_factory(name="test 1", id=1),
|
||||||
user_factory.user_public_factory(name="test 2", id=2),
|
user_factory.user_public_factory(name="test 2", id=2),
|
||||||
@@ -30,7 +35,15 @@ class TestUsers:
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
def test_get_all_filters(self, client, mocker, mock_session, mock_user):
|
assert mock_user
|
||||||
|
|
||||||
|
def test_get_all_filters(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_results = [
|
mock_results = [
|
||||||
user_factory.user_public_factory(name="test 2", id=2),
|
user_factory.user_public_factory(name="test 2", id=2),
|
||||||
]
|
]
|
||||||
@@ -39,7 +52,6 @@ class TestUsers:
|
|||||||
'get_all',
|
'get_all',
|
||||||
return_value=mock_results
|
return_value=mock_results
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/api/users?emails=test@test.test&names=test 2')
|
response = client.get('/api/users?emails=test@test.test&names=test 2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -50,8 +62,13 @@ class TestUsers:
|
|||||||
['test 2'],
|
['test 2'],
|
||||||
['test@test.test'],
|
['test@test.test'],
|
||||||
)
|
)
|
||||||
|
assert mock_user
|
||||||
|
|
||||||
def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_all_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -65,7 +82,13 @@ class TestUsers:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_get_one(self, client, mocker, mock_session, mock_user):
|
def test_get_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = user_factory.user_public_factory(name="test 2", id=2)
|
mock_result = user_factory.user_public_factory(name="test 2", id=2)
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -73,6 +96,11 @@ class TestUsers:
|
|||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.get('/api/users/2')
|
response = client.get('/api/users/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -83,23 +111,43 @@ class TestUsers:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_get_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock_result = None
|
mock_result = None
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'get_one',
|
'get_one',
|
||||||
return_value=mock_result
|
return_value=mock_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
response = client.get('/api/users/2')
|
response = client.get('/api/users/2')
|
||||||
response_data = response.json()
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_get_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
@@ -113,7 +161,13 @@ class TestUsers:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_create_one(self, client, mocker, mock_session, mock_user):
|
def test_create_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
user_body = user_factory.user_body_factory(name='test user create')
|
user_body = user_factory.user_body_factory(name='test user create')
|
||||||
user_create = user_factory.user_create_factory(name='test user create')
|
user_create = user_factory.user_create_factory(name='test user create')
|
||||||
user_result = user_factory.user_public_factory(name='test user create')
|
user_result = user_factory.user_public_factory(name='test user create')
|
||||||
@@ -123,6 +177,11 @@ class TestUsers:
|
|||||||
'create_one',
|
'create_one',
|
||||||
return_value=user_result
|
return_value=user_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.post('/api/users', json=user_body)
|
response = client.post('/api/users', json=user_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -133,8 +192,15 @@ class TestUsers:
|
|||||||
mock_session,
|
mock_session,
|
||||||
user_create
|
user_create
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_create_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
user_body = user_factory.user_body_factory(name='test user create')
|
user_body = user_factory.user_body_factory(name='test user create')
|
||||||
@@ -149,7 +215,13 @@ class TestUsers:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_update_one(self, client, mocker, mock_session, mock_user):
|
def test_update_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
user_body = user_factory.user_body_factory(name='test user update')
|
user_body = user_factory.user_body_factory(name='test user update')
|
||||||
user_update = user_factory.user_update_factory(name='test user update')
|
user_update = user_factory.user_update_factory(name='test user update')
|
||||||
user_result = user_factory.user_public_factory(name='test user update')
|
user_result = user_factory.user_public_factory(name='test user update')
|
||||||
@@ -159,6 +231,11 @@ class TestUsers:
|
|||||||
'update_one',
|
'update_one',
|
||||||
return_value=user_result
|
return_value=user_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.put('/api/users/2', json=user_body)
|
response = client.put('/api/users/2', json=user_body)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -170,20 +247,32 @@ class TestUsers:
|
|||||||
2,
|
2,
|
||||||
user_update
|
user_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_update_one_notfound(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
user_body = user_factory.user_body_factory(name='test user update')
|
user_body = user_factory.user_body_factory(name='test user update')
|
||||||
user_update = user_factory.user_update_factory(name='test user update')
|
user_update = user_factory.user_update_factory(name='test user update')
|
||||||
user_result = None
|
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'update_one',
|
'update_one',
|
||||||
side_effect=exceptions.UserNotFoundError('User 2 not found')
|
side_effect=exceptions.UserNotFoundError('User 2 not found')
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.put('/api/users/2', json=user_body)
|
response = client.put('/api/users/2', json=user_body)
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
@@ -191,8 +280,15 @@ class TestUsers:
|
|||||||
2,
|
2,
|
||||||
user_update
|
user_update
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_update_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
user_body = user_factory.user_body_factory(name='test user update')
|
user_body = user_factory.user_body_factory(name='test user update')
|
||||||
@@ -207,7 +303,13 @@ class TestUsers:
|
|||||||
|
|
||||||
app.dependency_overrides.clear()
|
app.dependency_overrides.clear()
|
||||||
|
|
||||||
def test_delete_one(self, client, mocker, mock_session, mock_user):
|
def test_delete_one(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
user_result = user_factory.user_public_factory(name='test user delete')
|
user_result = user_factory.user_public_factory(name='test user delete')
|
||||||
|
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
@@ -215,6 +317,11 @@ class TestUsers:
|
|||||||
'delete_one',
|
'delete_one',
|
||||||
return_value=user_result
|
return_value=user_result
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.delete('/api/users/2')
|
response = client.delete('/api/users/2')
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
@@ -225,30 +332,46 @@ class TestUsers:
|
|||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_notfound(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_notfound(
|
||||||
user_result = None
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
mock_session,
|
||||||
|
mock_user,
|
||||||
|
):
|
||||||
mock = mocker.patch.object(
|
mock = mocker.patch.object(
|
||||||
service,
|
service,
|
||||||
'delete_one',
|
'delete_one',
|
||||||
side_effect=exceptions.UserNotFoundError('User 2 not found')
|
side_effect=exceptions.UserNotFoundError('User 2 not found')
|
||||||
)
|
)
|
||||||
|
mock_is_allowed = mocker.patch.object(
|
||||||
|
service,
|
||||||
|
'is_allowed',
|
||||||
|
return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
response = client.delete('/api/users/2')
|
response = client.delete('/api/users/2')
|
||||||
response_data = response.json()
|
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
mock.assert_called_once_with(
|
mock.assert_called_once_with(
|
||||||
mock_session,
|
mock_session,
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
|
mock_is_allowed.assert_called_once_with(
|
||||||
|
mock_user
|
||||||
|
)
|
||||||
|
|
||||||
def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user):
|
def test_delete_one_unauthorized(
|
||||||
|
self,
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
def unauthorized():
|
def unauthorized():
|
||||||
raise HTTPException(status_code=401)
|
raise HTTPException(status_code=401)
|
||||||
user_body = user_factory.user_body_factory(name='test user delete')
|
|
||||||
|
|
||||||
app.dependency_overrides[get_current_user] = unauthorized
|
app.dependency_overrides[get_current_user] = unauthorized
|
||||||
|
|
||||||
mock = mocker.patch('src.users.service.delete_one')
|
mock = mocker.patch('src.users.service.delete_one')
|
||||||
|
|||||||
3
backend/tests/services/__init__.py
Normal file
3
backend/tests/services/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026-present Julien Aldon <julien.aldon@wanadoo.fr>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
@@ -1,35 +1,41 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.forms.service as forms_service
|
|
||||||
import src.forms.exceptions as forms_exceptions
|
import src.forms.exceptions as forms_exceptions
|
||||||
|
import src.forms.service as forms_service
|
||||||
import tests.factories.forms as forms_factory
|
import tests.factories.forms as forms_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
class TestFormsService:
|
class TestFormsService:
|
||||||
def test_get_all_forms(self, session: Session, forms: list[models.FormPublic]):
|
def test_get_all_forms(self, session: Session,
|
||||||
|
forms: list[models.FormPublic]):
|
||||||
result = forms_service.get_all(session, [], [], False)
|
result = forms_service.get_all(session, [], [], False)
|
||||||
|
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
assert result == forms
|
assert result == forms
|
||||||
|
|
||||||
def test_get_all_forms_filter_productors(self, session: Session, forms: list[models.FormPublic]):
|
def test_get_all_forms_filter_productors(
|
||||||
|
self, session: Session, forms: list[models.FormPublic]):
|
||||||
result = forms_service.get_all(session, [], ['test productor'], False)
|
result = forms_service.get_all(session, [], ['test productor'], False)
|
||||||
|
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
assert result == forms
|
assert result == forms
|
||||||
|
|
||||||
def test_get_all_forms_filter_season(self, session: Session, forms: list[models.FormPublic]):
|
def test_get_all_forms_filter_season(
|
||||||
|
self, session: Session, forms: list[models.FormPublic]):
|
||||||
result = forms_service.get_all(session, ['test season 1'], [], False)
|
result = forms_service.get_all(session, ['test season 1'], [], False)
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_all_forms_all_filters(self, session: Session, forms: list[models.FormPublic]):
|
def test_get_all_forms_all_filters(
|
||||||
result = forms_service.get_all(session, ['test season 1'], ['test productor'], True)
|
self, session: Session, forms: list[models.FormPublic]):
|
||||||
|
result = forms_service.get_all(
|
||||||
|
session, ['test season 1'], ['test productor'], True)
|
||||||
|
|
||||||
assert result == forms
|
assert result == forms
|
||||||
|
|
||||||
def test_get_one_form(self, session: Session, forms: list[models.FormPublic]):
|
def test_get_one_form(self, session: Session,
|
||||||
|
forms: list[models.FormPublic]):
|
||||||
result = forms_service.get_one(session, forms[0].id)
|
result = forms_service.get_one(session, forms[0].id)
|
||||||
|
|
||||||
assert result == forms[0]
|
assert result == forms[0]
|
||||||
@@ -37,7 +43,7 @@ class TestFormsService:
|
|||||||
def test_get_one_form_notfound(self, session: Session):
|
def test_get_one_form_notfound(self, session: Session):
|
||||||
result = forms_service.get_one(session, 122)
|
result = forms_service.get_one(session, 122)
|
||||||
|
|
||||||
assert result == None
|
assert result is None
|
||||||
|
|
||||||
def test_create_form(
|
def test_create_form(
|
||||||
self,
|
self,
|
||||||
@@ -77,7 +83,6 @@ class TestFormsService:
|
|||||||
with pytest.raises(forms_exceptions.UserNotFoundError):
|
with pytest.raises(forms_exceptions.UserNotFoundError):
|
||||||
result = forms_service.create_one(session, form_create)
|
result = forms_service.create_one(session, form_create)
|
||||||
|
|
||||||
|
|
||||||
def test_update_form(
|
def test_update_form(
|
||||||
self,
|
self,
|
||||||
session: Session,
|
session: Session,
|
||||||
@@ -141,7 +146,7 @@ class TestFormsService:
|
|||||||
result = forms_service.delete_one(session, form_id)
|
result = forms_service.delete_one(session, form_id)
|
||||||
|
|
||||||
check = forms_service.get_one(session, form_id)
|
check = forms_service.get_one(session, form_id)
|
||||||
assert check == None
|
assert check is None
|
||||||
|
|
||||||
def test_delete_form_notfound(
|
def test_delete_form_notfound(
|
||||||
self,
|
self,
|
||||||
@@ -151,4 +156,3 @@ class TestFormsService:
|
|||||||
form_id = 123
|
form_id = 123
|
||||||
with pytest.raises(forms_exceptions.FormNotFoundError):
|
with pytest.raises(forms_exceptions.FormNotFoundError):
|
||||||
result = forms_service.delete_one(session, form_id)
|
result = forms_service.delete_one(session, form_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.productors.service as productors_service
|
|
||||||
import src.productors.exceptions as productors_exceptions
|
import src.productors.exceptions as productors_exceptions
|
||||||
|
import src.productors.service as productors_service
|
||||||
import tests.factories.productors as productors_factory
|
import tests.factories.productors as productors_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
class TestProductorsService:
|
class TestProductorsService:
|
||||||
def test_get_all_productors(
|
def test_get_all_productors(
|
||||||
@@ -63,7 +63,9 @@ class TestProductorsService:
|
|||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_one_productor(self, session: Session, productors: list[models.ProductorPublic]):
|
def test_get_one_productor(self,
|
||||||
|
session: Session,
|
||||||
|
productors: list[models.ProductorPublic]):
|
||||||
result = productors_service.get_one(session, productors[0].id)
|
result = productors_service.get_one(session, productors[0].id)
|
||||||
|
|
||||||
assert result == productors[0]
|
assert result == productors[0]
|
||||||
@@ -71,7 +73,7 @@ class TestProductorsService:
|
|||||||
def test_get_one_productor_notfound(self, session: Session):
|
def test_get_one_productor_notfound(self, session: Session):
|
||||||
result = productors_service.get_one(session, 122)
|
result = productors_service.get_one(session, 122)
|
||||||
|
|
||||||
assert result == None
|
assert result is None
|
||||||
|
|
||||||
def test_create_productor(
|
def test_create_productor(
|
||||||
self,
|
self,
|
||||||
@@ -104,7 +106,8 @@ class TestProductorsService:
|
|||||||
name='updated test productor',
|
name='updated test productor',
|
||||||
)
|
)
|
||||||
productor_id = productors[0].id
|
productor_id = productors[0].id
|
||||||
result = productors_service.update_one(session, productor_id, productor_update)
|
result = productors_service.update_one(
|
||||||
|
session, productor_id, productor_update)
|
||||||
|
|
||||||
assert result.id == productor_id
|
assert result.id == productor_id
|
||||||
assert result.name == 'updated test productor'
|
assert result.name == 'updated test productor'
|
||||||
@@ -119,7 +122,8 @@ class TestProductorsService:
|
|||||||
)
|
)
|
||||||
productor_id = 123
|
productor_id = 123
|
||||||
with pytest.raises(productors_exceptions.ProductorNotFoundError):
|
with pytest.raises(productors_exceptions.ProductorNotFoundError):
|
||||||
result = productors_service.update_one(session, productor_id, productor_update)
|
result = productors_service.update_one(
|
||||||
|
session, productor_id, productor_update)
|
||||||
|
|
||||||
def test_delete_productor(
|
def test_delete_productor(
|
||||||
self,
|
self,
|
||||||
@@ -130,7 +134,7 @@ class TestProductorsService:
|
|||||||
result = productors_service.delete_one(session, productor_id)
|
result = productors_service.delete_one(session, productor_id)
|
||||||
|
|
||||||
check = productors_service.get_one(session, productor_id)
|
check = productors_service.get_one(session, productor_id)
|
||||||
assert check == None
|
assert check is None
|
||||||
|
|
||||||
def test_delete_productor_notfound(
|
def test_delete_productor_notfound(
|
||||||
self,
|
self,
|
||||||
@@ -140,4 +144,3 @@ class TestProductorsService:
|
|||||||
productor_id = 123
|
productor_id = 123
|
||||||
with pytest.raises(productors_exceptions.ProductorNotFoundError):
|
with pytest.raises(productors_exceptions.ProductorNotFoundError):
|
||||||
result = productors_service.delete_one(session, productor_id)
|
result = productors_service.delete_one(session, productor_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.products.service as products_service
|
|
||||||
import src.products.exceptions as products_exceptions
|
import src.products.exceptions as products_exceptions
|
||||||
|
import src.products.service as products_service
|
||||||
import tests.factories.products as products_factory
|
import tests.factories.products as products_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
class TestProductsService:
|
class TestProductsService:
|
||||||
def test_get_all_products(
|
def test_get_all_products(
|
||||||
@@ -83,7 +83,8 @@ class TestProductsService:
|
|||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_one_product(self, session: Session, products: list[models.ProductPublic]):
|
def test_get_one_product(self, session: Session,
|
||||||
|
products: list[models.ProductPublic]):
|
||||||
result = products_service.get_one(session, products[0].id)
|
result = products_service.get_one(session, products[0].id)
|
||||||
|
|
||||||
assert result == products[0]
|
assert result == products[0]
|
||||||
@@ -91,7 +92,7 @@ class TestProductsService:
|
|||||||
def test_get_one_product_notfound(self, session: Session):
|
def test_get_one_product_notfound(self, session: Session):
|
||||||
result = products_service.get_one(session, 122)
|
result = products_service.get_one(session, 122)
|
||||||
|
|
||||||
assert result == None
|
assert result is None
|
||||||
|
|
||||||
def test_create_product(
|
def test_create_product(
|
||||||
self,
|
self,
|
||||||
@@ -118,7 +119,8 @@ class TestProductsService:
|
|||||||
with pytest.raises(products_exceptions.ProductCreateError):
|
with pytest.raises(products_exceptions.ProductCreateError):
|
||||||
result = products_service.create_one(session, product_create)
|
result = products_service.create_one(session, product_create)
|
||||||
|
|
||||||
product_create = products_factory.product_create_factory(productor_id=123)
|
product_create = products_factory.product_create_factory(
|
||||||
|
productor_id=123)
|
||||||
with pytest.raises(products_exceptions.ProductorNotFoundError):
|
with pytest.raises(products_exceptions.ProductorNotFoundError):
|
||||||
result = products_service.create_one(session, product_create)
|
result = products_service.create_one(session, product_create)
|
||||||
|
|
||||||
@@ -134,7 +136,8 @@ class TestProductsService:
|
|||||||
productor_id=productor.id,
|
productor_id=productor.id,
|
||||||
)
|
)
|
||||||
product_id = products[0].id
|
product_id = products[0].id
|
||||||
result = products_service.update_one(session, product_id, product_update)
|
result = products_service.update_one(
|
||||||
|
session, product_id, product_update)
|
||||||
|
|
||||||
assert result.id == product_id
|
assert result.id == product_id
|
||||||
assert result.name == 'updated test product'
|
assert result.name == 'updated test product'
|
||||||
@@ -151,7 +154,8 @@ class TestProductsService:
|
|||||||
)
|
)
|
||||||
product_id = 123
|
product_id = 123
|
||||||
with pytest.raises(products_exceptions.ProductNotFoundError):
|
with pytest.raises(products_exceptions.ProductNotFoundError):
|
||||||
result = products_service.update_one(session, product_id, product_update)
|
result = products_service.update_one(
|
||||||
|
session, product_id, product_update)
|
||||||
|
|
||||||
def test_update_product_invalidinput(
|
def test_update_product_invalidinput(
|
||||||
self,
|
self,
|
||||||
@@ -160,9 +164,11 @@ class TestProductsService:
|
|||||||
products: list[models.ProductPublic]
|
products: list[models.ProductPublic]
|
||||||
):
|
):
|
||||||
product_id = products[0].id
|
product_id = products[0].id
|
||||||
product_update = products_factory.product_update_factory(productor_id=123)
|
product_update = products_factory.product_update_factory(
|
||||||
|
productor_id=123)
|
||||||
with pytest.raises(products_exceptions.ProductorNotFoundError):
|
with pytest.raises(products_exceptions.ProductorNotFoundError):
|
||||||
result = products_service.update_one(session, product_id, product_update)
|
result = products_service.update_one(
|
||||||
|
session, product_id, product_update)
|
||||||
|
|
||||||
product_update = products_factory.product_update_factory(
|
product_update = products_factory.product_update_factory(
|
||||||
productor_id=productor.id,
|
productor_id=productor.id,
|
||||||
@@ -178,7 +184,7 @@ class TestProductsService:
|
|||||||
result = products_service.delete_one(session, product_id)
|
result = products_service.delete_one(session, product_id)
|
||||||
|
|
||||||
check = products_service.get_one(session, product_id)
|
check = products_service.get_one(session, product_id)
|
||||||
assert check == None
|
assert check is None
|
||||||
|
|
||||||
def test_delete_product_notfound(
|
def test_delete_product_notfound(
|
||||||
self,
|
self,
|
||||||
@@ -188,4 +194,3 @@ class TestProductsService:
|
|||||||
product_id = 123
|
product_id = 123
|
||||||
with pytest.raises(products_exceptions.ProductNotFoundError):
|
with pytest.raises(products_exceptions.ProductNotFoundError):
|
||||||
result = products_service.delete_one(session, product_id)
|
result = products_service.delete_one(session, product_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import pytest
|
|
||||||
import datetime
|
import datetime
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
import pytest
|
||||||
import src.shipments.service as shipments_service
|
|
||||||
import src.shipments.exceptions as shipments_exceptions
|
import src.shipments.exceptions as shipments_exceptions
|
||||||
|
import src.shipments.service as shipments_service
|
||||||
import tests.factories.shipments as shipments_factory
|
import tests.factories.shipments as shipments_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
class TestShipmentsService:
|
class TestShipmentsService:
|
||||||
def test_get_all_shipments(
|
def test_get_all_shipments(
|
||||||
@@ -25,7 +26,8 @@ class TestShipmentsService:
|
|||||||
shipments: list[models.ShipmentPublic],
|
shipments: list[models.ShipmentPublic],
|
||||||
user: models.UserPublic,
|
user: models.UserPublic,
|
||||||
):
|
):
|
||||||
result = shipments_service.get_all(session, user, ['test shipment 1'], [], [])
|
result = shipments_service.get_all(
|
||||||
|
session, user, ['test shipment 1'], [], [])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result == [shipments[0]]
|
assert result == [shipments[0]]
|
||||||
@@ -36,7 +38,8 @@ class TestShipmentsService:
|
|||||||
shipments: list[models.ShipmentPublic],
|
shipments: list[models.ShipmentPublic],
|
||||||
user: models.UserPublic,
|
user: models.UserPublic,
|
||||||
):
|
):
|
||||||
result = shipments_service.get_all(session, user, [], ['2025-10-10'], [])
|
result = shipments_service.get_all(
|
||||||
|
session, user, [], ['2025-10-10'], [])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
@@ -47,7 +50,8 @@ class TestShipmentsService:
|
|||||||
forms: list[models.FormPublic],
|
forms: list[models.FormPublic],
|
||||||
user: models.UserPublic,
|
user: models.UserPublic,
|
||||||
):
|
):
|
||||||
result = shipments_service.get_all(session, user, [], [], [forms[0].name])
|
result = shipments_service.get_all(
|
||||||
|
session, user, [], [], [forms[0].name])
|
||||||
|
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
|
|
||||||
@@ -58,11 +62,13 @@ class TestShipmentsService:
|
|||||||
forms: list[models.FormPublic],
|
forms: list[models.FormPublic],
|
||||||
user: models.UserPublic,
|
user: models.UserPublic,
|
||||||
):
|
):
|
||||||
result = shipments_service.get_all(session, user, ['test shipment 1'], ['2025-10-10'], [forms[0].name])
|
result = shipments_service.get_all(session, user, ['test shipment 1'], [
|
||||||
|
'2025-10-10'], [forms[0].name])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_one_shipment(self, session: Session, shipments: list[models.ShipmentPublic]):
|
def test_get_one_shipment(self, session: Session,
|
||||||
|
shipments: list[models.ShipmentPublic]):
|
||||||
result = shipments_service.get_one(session, shipments[0].id)
|
result = shipments_service.get_one(session, shipments[0].id)
|
||||||
|
|
||||||
assert result == shipments[0]
|
assert result == shipments[0]
|
||||||
@@ -70,7 +76,7 @@ class TestShipmentsService:
|
|||||||
def test_get_one_shipment_notfound(self, session: Session):
|
def test_get_one_shipment_notfound(self, session: Session):
|
||||||
result = shipments_service.get_one(session, 122)
|
result = shipments_service.get_one(session, 122)
|
||||||
|
|
||||||
assert result == None
|
assert result is None
|
||||||
|
|
||||||
def test_create_shipment(
|
def test_create_shipment(
|
||||||
self,
|
self,
|
||||||
@@ -103,7 +109,8 @@ class TestShipmentsService:
|
|||||||
date='2025-12-10',
|
date='2025-12-10',
|
||||||
)
|
)
|
||||||
shipment_id = shipments[0].id
|
shipment_id = shipments[0].id
|
||||||
result = shipments_service.update_one(session, shipment_id, shipment_update)
|
result = shipments_service.update_one(
|
||||||
|
session, shipment_id, shipment_update)
|
||||||
|
|
||||||
assert result.id == shipment_id
|
assert result.id == shipment_id
|
||||||
assert result.name == 'updated shipment 1'
|
assert result.name == 'updated shipment 1'
|
||||||
@@ -119,7 +126,8 @@ class TestShipmentsService:
|
|||||||
)
|
)
|
||||||
shipment_id = 123
|
shipment_id = 123
|
||||||
with pytest.raises(shipments_exceptions.ShipmentNotFoundError):
|
with pytest.raises(shipments_exceptions.ShipmentNotFoundError):
|
||||||
result = shipments_service.update_one(session, shipment_id, shipment_update)
|
result = shipments_service.update_one(
|
||||||
|
session, shipment_id, shipment_update)
|
||||||
|
|
||||||
def test_delete_shipment(
|
def test_delete_shipment(
|
||||||
self,
|
self,
|
||||||
@@ -130,7 +138,7 @@ class TestShipmentsService:
|
|||||||
result = shipments_service.delete_one(session, shipment_id)
|
result = shipments_service.delete_one(session, shipment_id)
|
||||||
|
|
||||||
check = shipments_service.get_one(session, shipment_id)
|
check = shipments_service.get_one(session, shipment_id)
|
||||||
assert check == None
|
assert check is None
|
||||||
|
|
||||||
def test_delete_shipment_notfound(
|
def test_delete_shipment_notfound(
|
||||||
self,
|
self,
|
||||||
@@ -140,4 +148,3 @@ class TestShipmentsService:
|
|||||||
shipment_id = 123
|
shipment_id = 123
|
||||||
with pytest.raises(shipments_exceptions.ShipmentNotFoundError):
|
with pytest.raises(shipments_exceptions.ShipmentNotFoundError):
|
||||||
result = shipments_service.delete_one(session, shipment_id)
|
result = shipments_service.delete_one(session, shipment_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,41 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlmodel import Session
|
|
||||||
|
|
||||||
import src.models as models
|
|
||||||
import src.users.service as users_service
|
|
||||||
import src.users.exceptions as users_exceptions
|
import src.users.exceptions as users_exceptions
|
||||||
|
import src.users.service as users_service
|
||||||
import tests.factories.users as users_factory
|
import tests.factories.users as users_factory
|
||||||
|
from sqlmodel import Session
|
||||||
|
from src import models
|
||||||
|
|
||||||
|
|
||||||
class TestUsersService:
|
class TestUsersService:
|
||||||
def test_get_all_users(self, session: Session, users: list[models.UserPublic]):
|
def test_get_all_users(self, session: Session,
|
||||||
|
users: list[models.UserPublic]):
|
||||||
result = users_service.get_all(session, [], [])
|
result = users_service.get_all(session, [], [])
|
||||||
|
|
||||||
assert len(result) == 3
|
assert len(result) == 3
|
||||||
assert result == users
|
assert result == users
|
||||||
|
|
||||||
def test_get_all_users_filter_names(self, session: Session, users: list[models.UserPublic]):
|
def test_get_all_users_filter_names(
|
||||||
|
self, session: Session, users: list[models.UserPublic]):
|
||||||
result = users_service.get_all(session, ['test user 1 (admin)'], [])
|
result = users_service.get_all(session, ['test user 1 (admin)'], [])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result == [users[0]]
|
assert result == [users[0]]
|
||||||
|
|
||||||
def test_get_all_users_filter_emails(self, session: Session, users: list[models.UserPublic]):
|
def test_get_all_users_filter_emails(
|
||||||
|
self, session: Session, users: list[models.UserPublic]):
|
||||||
result = users_service.get_all(session, [], ['test1@test.com'])
|
result = users_service.get_all(session, [], ['test1@test.com'])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_all_users_all_filters(self, session: Session, users: list[models.UserPublic]):
|
def test_get_all_users_all_filters(
|
||||||
result = users_service.get_all(session, ['test user 1 (admin)'], ['test1@test.com'])
|
self, session: Session, users: list[models.UserPublic]):
|
||||||
|
result = users_service.get_all(
|
||||||
|
session, ['test user 1 (admin)'], ['test1@test.com'])
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
def test_get_one_user(self, session: Session, users: list[models.UserPublic]):
|
def test_get_one_user(self, session: Session,
|
||||||
|
users: list[models.UserPublic]):
|
||||||
result = users_service.get_one(session, users[0].id)
|
result = users_service.get_one(session, users[0].id)
|
||||||
|
|
||||||
assert result == users[0]
|
assert result == users[0]
|
||||||
@@ -37,7 +43,7 @@ class TestUsersService:
|
|||||||
def test_get_one_user_notfound(self, session: Session):
|
def test_get_one_user_notfound(self, session: Session):
|
||||||
result = users_service.get_one(session, 122)
|
result = users_service.get_one(session, 122)
|
||||||
|
|
||||||
assert result == None
|
assert result is None
|
||||||
|
|
||||||
def test_create_user(
|
def test_create_user(
|
||||||
self,
|
self,
|
||||||
@@ -102,7 +108,7 @@ class TestUsersService:
|
|||||||
result = users_service.delete_one(session, user_id)
|
result = users_service.delete_one(session, user_id)
|
||||||
|
|
||||||
check = users_service.get_one(session, user_id)
|
check = users_service.get_one(session, user_id)
|
||||||
assert check == None
|
assert check is None
|
||||||
|
|
||||||
def test_delete_user_notfound(
|
def test_delete_user_notfound(
|
||||||
self,
|
self,
|
||||||
@@ -112,4 +118,3 @@ class TestUsersService:
|
|||||||
user_id = 123
|
user_id = 123
|
||||||
with pytest.raises(users_exceptions.UserNotFoundError):
|
with pytest.raises(users_exceptions.UserNotFoundError):
|
||||||
result = users_service.delete_one(session, user_id)
|
result = users_service.delete_one(session, user_id)
|
||||||
|
|
||||||
|
|||||||
@@ -1,214 +1,225 @@
|
|||||||
{
|
{
|
||||||
"help": "help",
|
"a address": "an address",
|
||||||
"how to use dashboard": "how to use the dashboard",
|
"a email": "an email address",
|
||||||
"product name": "product name",
|
"a end date": "an end date",
|
||||||
"product price": "product price",
|
"a fistname": "a first name",
|
||||||
"product quantity": "product quantity",
|
"a form": "a contract form",
|
||||||
"product quantity unit": "product quantity unit",
|
"a lastname": "a last name",
|
||||||
"product type": "product type",
|
"a name": "a name",
|
||||||
"occasional": "occasional",
|
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "a new contract form must be created for each new season. Do not edit past contracts.",
|
||||||
"occasional products": "occasional products per shipment",
|
"a payment method": "a payment method",
|
||||||
"select products per shipment": "select products for each shipment.",
|
"a phone": "a phone number",
|
||||||
"recurrent": "recurrent",
|
"a price": "a price",
|
||||||
"recurrent products": "recurrent products",
|
"a price or priceKg": "a price or price per kilogram",
|
||||||
"your selection in this category will apply for all shipments": "your selection will apply to all shipments (Example: For 6 shipments, the product will be counted 6 times: once per shipment).",
|
"a priceKg": "a price per kilogram",
|
||||||
"product price kg": "product price per kilogram",
|
"a product": "a product",
|
||||||
"product unit": "product sales unit",
|
"a product can be edited if its informations change, it should not be recreated for each contracts": "a product can be edited if information changes. It should not be recreated for each contract.",
|
||||||
"piece": "piece",
|
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "a product type defines how it will be organized in the final contract form. It can be recurrent or occasional. Recurrent products will be set for all shipments if selected. Occasional products can be chosen for each shipment.",
|
||||||
"in": "in",
|
"a productor": "a producer",
|
||||||
"enter quantity": "enter quantity",
|
"a productor can be edited if its informations change, it should not be recreated for each contracts": "a producer can be edited if information changes. It should not be recreated for each contract.",
|
||||||
"filter by season": "filter by season",
|
"a quantity": "a quantity",
|
||||||
"filter by form": "filter by form",
|
"a quantity unit": "a quantity unit",
|
||||||
"filter by productor": "filter by producer",
|
"a referer": "a referent",
|
||||||
"name": "name",
|
"a season": "a season",
|
||||||
"season": "season",
|
"a sell unit": "a sales unit",
|
||||||
"start": "start",
|
"a shipment": "a shipment",
|
||||||
"end": "end",
|
"a start date": "a start date",
|
||||||
"productor": "producer",
|
"a type": "a type",
|
||||||
"referer": "referent",
|
|
||||||
"edit form": "edit contract form",
|
|
||||||
"form name": "contract form name",
|
|
||||||
"contract season": "contract season",
|
|
||||||
"contract season recommandation": "recommendation: <Season>-<year> (Example: Winter-2025), if a form is already created, reuse it's season name.",
|
|
||||||
"start date": "start date",
|
|
||||||
"end date": "end date",
|
|
||||||
"nothing found": "nothing to display",
|
|
||||||
"number of shipment": "number of shipments",
|
|
||||||
"cancel": "cancel",
|
|
||||||
"create form": "create contract form",
|
|
||||||
"create productor": "create producer",
|
|
||||||
"edit productor": "edit producer",
|
|
||||||
"remove productor": "remove producer",
|
|
||||||
"home": "home",
|
|
||||||
"dashboard": "dashboard",
|
|
||||||
"filter by name": "filter by name",
|
|
||||||
"filter by type": "filter by type",
|
|
||||||
"address": "address",
|
|
||||||
"payment methods": "payment methods",
|
|
||||||
"type": "type",
|
|
||||||
"cheque": "cheque",
|
|
||||||
"transfer": "bank transfer",
|
|
||||||
"order name": "cheque payable to",
|
|
||||||
"productor name": "producer name",
|
|
||||||
"productor type": "producer type",
|
|
||||||
"productor address": "producer address",
|
|
||||||
"productor payment": "producer payment methods",
|
|
||||||
"priceKg": "price per kilogram",
|
|
||||||
"quantity": "quantity",
|
|
||||||
"quantity unit": "quantity unit",
|
|
||||||
"unit": "sales unit",
|
|
||||||
"price": "price",
|
|
||||||
"total price": "total price",
|
|
||||||
"create product": "create product",
|
|
||||||
"informations": "information",
|
|
||||||
"remove product": "remove product",
|
|
||||||
"edit product": "edit product",
|
|
||||||
"shipment name": "shipment name",
|
|
||||||
"shipment date": "shipment date",
|
|
||||||
"shipments": "shipments",
|
|
||||||
"shipment": "shipment",
|
|
||||||
"shipment products": "shipment products",
|
|
||||||
"shipment form": "shipment related form",
|
|
||||||
"minimum shipment value": "minimum shipment value (€)",
|
|
||||||
"shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "shipment products configuration is only necessary for occasional products (leave empty if all products are recurrent).",
|
|
||||||
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "recurrent products are for all shipments, occasional products are for a specific shipment (see shipment form).",
|
|
||||||
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "some contracts require a minimum value per shipment. Ignore this field if it does not apply to your contract.",
|
|
||||||
"export contracts": "export contracts",
|
|
||||||
"download recap": "download recap",
|
|
||||||
"fill contract online": "fill contract online",
|
|
||||||
"download base template to print": "download base template to print",
|
|
||||||
"to export contracts submissions before sending to the productor go to the contracts section": "to export contracts submissions before sending to the productor go to the contracts section.",
|
|
||||||
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
|
||||||
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
|
|
||||||
"in the same corner you can download a recap by clicking on the button": "in the same corner you can download a recap by clicking on the",
|
|
||||||
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
|
|
||||||
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form",
|
|
||||||
"contracts": "contracts",
|
|
||||||
"hidden": "hidden",
|
|
||||||
"visible": "visible",
|
|
||||||
"minimum price for this shipment should be at least": "minimum price for this shipment should be at least",
|
|
||||||
"there is": "there is",
|
|
||||||
"for this contract": "for this contract.",
|
|
||||||
"remove shipment": "remove shipment",
|
|
||||||
"productors": "producers",
|
|
||||||
"products": "products",
|
|
||||||
"templates": "templates",
|
|
||||||
"users": "users",
|
|
||||||
"forms": "contract forms",
|
|
||||||
"max cheque number": "max cheque number",
|
|
||||||
"can be empty default to 3": "can be empty default to 3",
|
|
||||||
"form": "contract form",
|
|
||||||
"select a form": "select a form",
|
|
||||||
"download contracts": "download contracts",
|
|
||||||
"all contracts": "all contracts",
|
|
||||||
"remove contract": "remove contract",
|
|
||||||
"download contract": "download contract",
|
|
||||||
"by selecting a form here you can download all contracts of your form": "by selecting a form here you can download all contracts of your form.",
|
|
||||||
"edit user": "edit user",
|
|
||||||
"remove user": "remove user",
|
|
||||||
"logout": "logout",
|
|
||||||
"all forms": "all contract forms",
|
|
||||||
"create new form": "create new contract form",
|
|
||||||
"actions": "actions",
|
"actions": "actions",
|
||||||
|
"add all products linked to this productor in the products section": "add your products linked to the producer in the \"Products\" section.",
|
||||||
|
"address": "address",
|
||||||
|
"all contracts": "all contracts",
|
||||||
|
"all forms": "all contract forms",
|
||||||
"all productors": "all producers",
|
"all productors": "all producers",
|
||||||
"all products": "all products",
|
"all products": "all products",
|
||||||
"all shipments": "all shipments",
|
|
||||||
"all referers": "all referents",
|
"all referers": "all referents",
|
||||||
"is required": "is required",
|
"all shipments": "all shipments",
|
||||||
"a name": "a name",
|
|
||||||
"a season": "a season",
|
|
||||||
"a start date": "a start date",
|
|
||||||
"a end date": "an end date",
|
|
||||||
"a productor": "a producer",
|
|
||||||
"a referer": "a referent",
|
|
||||||
"a phone": "a phone number",
|
|
||||||
"a fistname": "a first name",
|
|
||||||
"a lastname": "a last name",
|
|
||||||
"a email": "an email address",
|
|
||||||
"a price or priceKg": "a price or price per kilogram",
|
|
||||||
"a address": "an address",
|
|
||||||
"a type": "a type",
|
|
||||||
"a form": "a contract form",
|
|
||||||
"a price": "a price",
|
|
||||||
"a priceKg": "a price per kilogram",
|
|
||||||
"a quantity": "a quantity",
|
|
||||||
"a product": "a product",
|
|
||||||
"a quantity unit": "a quantity unit",
|
|
||||||
"a payment method": "a payment method",
|
|
||||||
"a sell unit": "a sales unit",
|
|
||||||
"sell unit": "sales unit",
|
|
||||||
"product": "product",
|
|
||||||
"a shipment": "a shipment",
|
|
||||||
"the products": "the products",
|
|
||||||
"the shipments": "the shipments",
|
|
||||||
"link to the section": "link to section: {{section}}",
|
|
||||||
"to add a use the": "to add {{section}} use the button",
|
|
||||||
"to edit a use the": "to edit {{section}} use the button",
|
|
||||||
"to delete a use the": "to delete {{section}} use the button",
|
|
||||||
"button in top right of the page": "at the top right of the {{section}} page.",
|
|
||||||
"button in front of the line you want to edit": "in front of the line you want to edit (in the actions column).",
|
|
||||||
"button in front of the line you want to delete": "in front of the line you want to delete (in the actions column).",
|
|
||||||
"glossary": "glossary",
|
|
||||||
"start to create a productor in the productors section": "start by creating a producer in the \"Producers\" section.",
|
|
||||||
"add all products linked to this productor in the products section": "add your products linked to the producer in the \"Products\" section.",
|
|
||||||
"create your contract form, it will create a form in the home page (accessible to users)": "create your contract form in the \"Contract Forms\" section. Adding an entry here will create a form on the home page.",
|
|
||||||
"create shipments for your contract form": "create shipments for your contract",
|
|
||||||
"creation order": "creation order",
|
|
||||||
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "the dashboard is only visible to referents. You can create your producer, products, contract forms, and shipments.",
|
|
||||||
"is defined by": "is defined by",
|
|
||||||
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "a product type defines how it will be organized in the final contract form. It can be recurrent or occasional. Recurrent products will be set for all shipments if selected. Occasional products can be chosen for each shipment.",
|
|
||||||
"and/or": "and/or",
|
|
||||||
"form name recommandation": "recommendation: Contract <contract-type> (Example: Pork-Lamb Contract)",
|
|
||||||
"submit contract": "submit contract",
|
|
||||||
"example in user forms": "example in user contract form",
|
|
||||||
"occasional product": "occasional product",
|
|
||||||
"recurrent product": "recurrent product",
|
|
||||||
"with grams as product unit selected": "with grams selected as product unit",
|
|
||||||
"product example": "product example",
|
|
||||||
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "payment methods are defined for a producer. At the end of the form, users can select their preferred payment method.",
|
|
||||||
"with cheque and transfer": "with cheque and transfer configured for the producer",
|
|
||||||
"mililiter": "milliliters (ml)",
|
|
||||||
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "this field is optional. It represents the product quantity and will be shown in the form.",
|
|
||||||
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "this field is optional. It represents the measurement unit and will be shown next to the quantity.",
|
|
||||||
"with 150 set as quantity and g as quantity unit in product": "with 150 set as quantity and grams selected as quantity unit",
|
|
||||||
"all shipments should be recreated for each form creation": "shipments must be recreated for each new contract form.",
|
"all shipments should be recreated for each form creation": "shipments must be recreated for each new contract form.",
|
||||||
"a productor can be edited if its informations change, it should not be recreated for each contracts": "a producer can be edited if information changes. It should not be recreated for each contract.",
|
"all theses informations are for contract generation": "all this information is required for contract generation.",
|
||||||
"a product can be edited if its informations change, it should not be recreated for each contracts": "a product can be edited if information changes. It should not be recreated for each contract.",
|
"and/or": "and/or",
|
||||||
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "a new contract form must be created for each new season. Do not edit past contracts.",
|
"are you sure you want to delete": "are you sure you want to delete",
|
||||||
"grams": "grams (g)",
|
"button in front of the line you want to delete": "in front of the line you want to delete (in the actions column).",
|
||||||
"kilo": "kilograms (kg)",
|
"button in front of the line you want to edit": "in front of the line you want to edit (in the actions column).",
|
||||||
"liter": "liters (L)",
|
"button in top right of the page": "at the top right of the {{section}} page.",
|
||||||
"success": "success",
|
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form",
|
||||||
"success edit": "{{entity}} correctly edited",
|
"by selecting a form here you can download all contracts of your form": "by selecting a form here you can download all contracts of your form.",
|
||||||
"success create": "{{entity}} correctly created",
|
"can be empty default to 3": "can be empty default to 3",
|
||||||
"success delete": "{{entity}} correctly deleted",
|
"cancel": "cancel",
|
||||||
|
"cheque": "cheque",
|
||||||
|
"cheque id": "cheque identifier",
|
||||||
|
"cheque quantity": "number of cheques",
|
||||||
|
"cheque value": "cheque amount",
|
||||||
|
"choose payment method": "choose your payment method (you do not need to pay now).",
|
||||||
|
"contract": "contract",
|
||||||
|
"contract season": "contract season",
|
||||||
|
"contract season recommandation": "recommendation: <Season>-<year> (Example: Winter-2025), if a form is already created, reuse it's season name.",
|
||||||
|
"contracts": "contracts",
|
||||||
|
"create form": "create contract form",
|
||||||
|
"create new form": "create new contract form",
|
||||||
|
"create product": "create product",
|
||||||
|
"create productor": "create producer",
|
||||||
|
"create shipment": "create shipment",
|
||||||
|
"create shipments for your contract form": "create shipments for your contract",
|
||||||
|
"create user": "create user",
|
||||||
|
"create your contract form, it will create a form in the home page (accessible to users)": "create your contract form in the \"Contract Forms\" section. Adding an entry here will create a form on the home page.",
|
||||||
|
"creation order": "creation order",
|
||||||
|
"dashboard": "dashboard",
|
||||||
|
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "the dashboard is only visible to referents. You can create your producer, products, contract forms, and shipments.",
|
||||||
|
"delete": "delete",
|
||||||
|
"delete entity": "delete {{entity}}",
|
||||||
|
"download base template to print": "download base template to print",
|
||||||
|
"download contract": "download contract",
|
||||||
|
"download contracts": "download contracts",
|
||||||
|
"download recap": "download recap",
|
||||||
|
"edit form": "edit contract form",
|
||||||
|
"edit product": "edit product",
|
||||||
|
"edit productor": "edit producer",
|
||||||
|
"edit shipment": "edit shipment",
|
||||||
|
"edit user": "edit user",
|
||||||
|
"end": "end",
|
||||||
|
"end date": "end date",
|
||||||
|
"enter cheque quantity": "enter number of cheques",
|
||||||
|
"enter cheque value": "enter cheque amount",
|
||||||
|
"enter payment method": "select your payment method",
|
||||||
|
"enter quantity": "enter quantity",
|
||||||
"error": "error",
|
"error": "error",
|
||||||
"error edit": "error during edit {{entity}}",
|
|
||||||
"error create": "error during create {{entity}}",
|
"error create": "error during create {{entity}}",
|
||||||
"error delete": "error during suppress {{entity}}",
|
"error delete": "error during suppress {{entity}}",
|
||||||
"of the user": "of the user",
|
"error edit": "error during edit {{entity}}",
|
||||||
|
"example in user forms": "example in user contract form",
|
||||||
|
"export contracts": "export contracts",
|
||||||
|
"fill contract online": "fill contract online",
|
||||||
|
"filter by form": "filter by form",
|
||||||
|
"filter by name": "filter by name",
|
||||||
|
"filter by productor": "filter by producer",
|
||||||
|
"filter by season": "filter by season",
|
||||||
|
"filter by type": "filter by type",
|
||||||
|
"for this contract": "for this contract.",
|
||||||
|
"for transfer method contact your referer or productor": "for bank transfer, contact your referent or producer.",
|
||||||
|
"form": "contract form",
|
||||||
|
"form name": "contract form name",
|
||||||
|
"form name recommandation": "recommendation: Contract <contract-type> (Example: Pork-Lamb Contract)",
|
||||||
|
"forms": "contract forms",
|
||||||
|
"glossary": "glossary",
|
||||||
|
"grams": "grams (g)",
|
||||||
|
"help": "help",
|
||||||
|
"hidden": "hidden",
|
||||||
|
"home": "home",
|
||||||
|
"how to use dashboard": "how to use the dashboard",
|
||||||
|
"in": "in",
|
||||||
|
"in the same corner you can download a recap by clicking on the button": "in the same corner you can download a recap by clicking on the",
|
||||||
|
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
||||||
|
"informations": "information",
|
||||||
|
"is defined by": "is defined by",
|
||||||
|
"is required": "is required",
|
||||||
|
"kilo": "kilograms (kg)",
|
||||||
|
"link to the section": "link to section: {{section}}",
|
||||||
|
"liter": "liters (L)",
|
||||||
|
"login with keycloak": "login with keycloak",
|
||||||
|
"logout": "logout",
|
||||||
|
"max cheque number": "max cheque number",
|
||||||
|
"mililiter": "milliliters (ml)",
|
||||||
|
"minimum price for this shipment should be at least": "minimum price for this shipment should be at least",
|
||||||
|
"minimum shipment value": "minimum shipment value (€)",
|
||||||
|
"name": "name",
|
||||||
|
"nothing found": "nothing to display",
|
||||||
|
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "number of cheques between 1 and 3. Also enter cheque identifiers.",
|
||||||
|
"number of shipment": "number of shipments",
|
||||||
|
"occasional": "occasional",
|
||||||
|
"occasional product": "occasional product",
|
||||||
|
"occasional products": "occasional products per shipment",
|
||||||
|
"of the contract": "of the contract",
|
||||||
"of the form": "of the form",
|
"of the form": "of the form",
|
||||||
"of the product": "of the product",
|
"of the product": "of the product",
|
||||||
"of the productor": "of the producer",
|
"of the productor": "of the producer",
|
||||||
"of the shipment": "of the shipment",
|
"of the shipment": "of the shipment",
|
||||||
"of the contract": "of the contract",
|
"of the user": "of the user",
|
||||||
"login with keycloak": "login with keycloak",
|
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
|
||||||
"there is no contract for now": "there is no contract at the moment.",
|
"order name": "cheque payable to",
|
||||||
"for transfer method contact your referer or productor": "for bank transfer, contact your referent or producer.",
|
|
||||||
"cheque quantity": "number of cheques",
|
|
||||||
"enter cheque quantity": "enter number of cheques",
|
|
||||||
"cheque id": "cheque identifier",
|
|
||||||
"cheque value": "cheque amount",
|
|
||||||
"enter cheque value": "enter cheque amount",
|
|
||||||
"enter payment method": "select your payment method",
|
|
||||||
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "number of cheques between 1 and 3. Also enter cheque identifiers.",
|
|
||||||
"payment method": "payment method",
|
"payment method": "payment method",
|
||||||
"your session has expired please log in again": "your session has expired please log in again",
|
"payment methods": "payment methods",
|
||||||
"session expired": "session expired",
|
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "payment methods are defined for a producer. At the end of the form, users can select their preferred payment method.",
|
||||||
"user not allowed": "user not allowed",
|
"piece": "piece",
|
||||||
|
"price": "price",
|
||||||
|
"priceKg": "price per kilogram",
|
||||||
|
"product": "product",
|
||||||
|
"product example": "product example",
|
||||||
|
"product name": "product name",
|
||||||
|
"product price": "product price",
|
||||||
|
"product price kg": "product price per kilogram",
|
||||||
|
"product quantity": "product quantity",
|
||||||
|
"product quantity unit": "product quantity unit",
|
||||||
|
"product type": "product type",
|
||||||
|
"product unit": "product sales unit",
|
||||||
|
"productor": "producer",
|
||||||
|
"productor address": "producer address",
|
||||||
|
"productor name": "producer name",
|
||||||
|
"productor payment": "producer payment methods",
|
||||||
|
"productor type": "producer type",
|
||||||
|
"productors": "producers",
|
||||||
|
"products": "products",
|
||||||
|
"quantity": "quantity",
|
||||||
|
"quantity unit": "quantity unit",
|
||||||
|
"recurrent": "recurrent",
|
||||||
|
"recurrent product": "recurrent product",
|
||||||
|
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "recurrent products are for all shipments, occasional products are for a specific shipment (see shipment form).",
|
||||||
|
"recurrent products": "recurrent products",
|
||||||
|
"referer": "referent",
|
||||||
|
"remove contract": "remove contract",
|
||||||
|
"remove form": "remove form",
|
||||||
|
"remove product": "remove product",
|
||||||
|
"remove productor": "remove producer",
|
||||||
|
"remove shipment": "remove shipment",
|
||||||
|
"remove user": "remove user",
|
||||||
"roles": "roles",
|
"roles": "roles",
|
||||||
"your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator",
|
"season": "season",
|
||||||
"choose payment method": "choose your payment method (you do not need to pay now).",
|
"select a form": "select a form",
|
||||||
|
"select products per shipment": "select products for each shipment.",
|
||||||
|
"sell unit": "sales unit",
|
||||||
|
"session expired": "session expired",
|
||||||
|
"shipment": "shipment",
|
||||||
|
"shipment date": "shipment date",
|
||||||
|
"shipment form": "shipment related form",
|
||||||
|
"shipment name": "shipment name",
|
||||||
|
"shipment products": "shipment products",
|
||||||
|
"shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "shipment products configuration is only necessary for occasional products (leave empty if all products are recurrent).",
|
||||||
|
"shipments": "shipments",
|
||||||
|
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "some contracts require a minimum value per shipment. Ignore this field if it does not apply to your contract.",
|
||||||
|
"start": "start",
|
||||||
|
"start date": "start date",
|
||||||
|
"start to create a productor in the productors section": "start by creating a producer in the \"Producers\" section.",
|
||||||
|
"submit": "submit",
|
||||||
|
"submit contract": "submit contract",
|
||||||
|
"success": "success",
|
||||||
|
"success create": "{{entity}} correctly created",
|
||||||
|
"success delete": "{{entity}} correctly deleted",
|
||||||
|
"success edit": "{{entity}} correctly edited",
|
||||||
|
"templates": "templates",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
|
"the product unit will be assigned to the quantity requested in the form": "the product unit defines the unit used in the contract form.",
|
||||||
"all theses informations are for contract generation": "all this information is required for contract generation."
|
"the products": "the products",
|
||||||
|
"the shipments": "the shipments",
|
||||||
|
"there is": "there is",
|
||||||
|
"there is no contract for now": "there is no contract at the moment.",
|
||||||
|
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "this field is optional. It represents the measurement unit and will be shown next to the quantity.",
|
||||||
|
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "this field is optional. It represents the product quantity and will be shown in the form.",
|
||||||
|
"this will also delete": "this will also delete",
|
||||||
|
"to add a use the": "to add {{section}} use the button",
|
||||||
|
"to delete a use the": "to delete {{section}} use the button",
|
||||||
|
"to edit a use the": "to edit {{section}} use the button",
|
||||||
|
"to export contracts submissions before sending to the productor go to the contracts section": "to export contracts submissions before sending to the productor go to the contracts section.",
|
||||||
|
"total price": "total price",
|
||||||
|
"transfer": "bank transfer",
|
||||||
|
"type": "type",
|
||||||
|
"unit": "sales unit",
|
||||||
|
"user": "user",
|
||||||
|
"user not allowed": "user not allowed",
|
||||||
|
"users": "users",
|
||||||
|
"visible": "visible",
|
||||||
|
"with 150 set as quantity and g as quantity unit in product": "with 150 set as quantity and grams selected as quantity unit",
|
||||||
|
"with cheque and transfer": "with cheque and transfer configured for the producer",
|
||||||
|
"with grams as product unit selected": "with grams selected as product unit",
|
||||||
|
"you can download all contracts for your form using the export all": "you can download all contracts for your form using the export all",
|
||||||
|
"your keycloak user has no roles, please contact your administrator": "your keycloak user has no roles, please contact your administrator",
|
||||||
|
"your selection in this category will apply for all shipments": "your selection will apply to all shipments (Example: For 6 shipments, the product will be counted 6 times: once per shipment).",
|
||||||
|
"your session has expired please log in again": "your session has expired please log in again"
|
||||||
}
|
}
|
||||||
@@ -1,214 +1,225 @@
|
|||||||
{
|
{
|
||||||
"help": "aide",
|
"a address": "une adresse",
|
||||||
"how to use dashboard": "comment utiliser le tableau de bord",
|
"a email": "une adresse email",
|
||||||
"product name": "nom du produit",
|
"a end date": "une date de fin",
|
||||||
"product price": "prix du produit",
|
"a fistname": "un prénom",
|
||||||
"product quantity": "quantité du produit",
|
"a form": "un formulaire de contrat",
|
||||||
"product quantity unit": "unité de quantité du produit",
|
"a lastname": "un nom",
|
||||||
"product type": "type de produit",
|
"a name": "un nom",
|
||||||
"occasional": "occasionnel",
|
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "un formulaire de contrat doit être créé pour chaque nouvelle saison, pour des raison d'historique, n'éditez pas un formulaire de contrat passé pour une nouvelle saison, recréez en un nouveau.",
|
||||||
"occasional products": "produits occasionnels par livraison",
|
"a payment method": "une méthode de paiement",
|
||||||
"select products per shipment": "sélectionnez les produits pour chaque livraison.",
|
"a phone": "un numéro de téléphone",
|
||||||
"recurrent": "récurent",
|
"a price": "un prix",
|
||||||
"recurrent products": "produits récurrents",
|
"a price or priceKg": "un prix ou un prix au kilo",
|
||||||
"your selection in this category will apply for all shipments": "votre sélection sera appliquée pour chaque livraisons (Exemple: Pour 6 livraisons, le produits sera compté 6 fois : une fois par livraison).",
|
"a priceKg": "un prix au kilo",
|
||||||
"product price kg": "prix du produit au Kilo",
|
"a product": "un produit",
|
||||||
"product unit": "unité de vente du produit",
|
"a product can be edited if its informations change, it should not be recreated for each contracts": "un produit peut être édité si ses informations changent, il ne doit pas être recréé pour chaque nouveau formulaire de contrat.",
|
||||||
"piece": "pièce",
|
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "un type de produit définit la manière dont un produit va être présenté aux amapiens dans le formulaire de contrat. Il peut être récurrent ou occasionnel. Un produit récurrent si selectionné sera compté pour toutes les livraisons. Un produit occasionnel sera facultatif pour chaques livraison (l'amapien devra selectionner la quantité voulue pour chaque livraisons).",
|
||||||
"in": "en",
|
"a productor": "un(e) producteur·trice",
|
||||||
"enter quantity": "entrez la quantité",
|
"a productor can be edited if its informations change, it should not be recreated for each contracts": "un(e) producteur·trice peut être édité si ses informations changent, il/elle ne doit pas être recréé pour chaque nouveau contrat.",
|
||||||
"filter by season": "filtrer par saisons",
|
"a quantity": "une quantité",
|
||||||
"filter by form": "filtrer par formulaire",
|
"a quantity unit": "une unité de quantité",
|
||||||
"filter by productor": "filtrer par producteur·trice",
|
"a referer": "un(e) référent·e",
|
||||||
"name": "nom",
|
"a season": "une saison",
|
||||||
"season": "saison",
|
"a sell unit": "une unité de vente",
|
||||||
"start": "début",
|
"a shipment": "une livraison",
|
||||||
"end": "fin",
|
"a start date": "une date de début",
|
||||||
"productor": "producteur·trice",
|
"a type": "un type",
|
||||||
"referer": "référent·e",
|
|
||||||
"edit form": "modifier le formulaire de contrat",
|
|
||||||
"form name": "nom du formulaire de contrat",
|
|
||||||
"contract season": "saison du contrat",
|
|
||||||
"contract season recommandation": "recommandation : <Saison>-<année> (Exemple: Hiver-2025), si un formulaire est déjà créé pour la saison, reprenez son nom de saison si possible.",
|
|
||||||
"start date": "date de début",
|
|
||||||
"end date": "date de fin",
|
|
||||||
"nothing found": "rien à afficher",
|
|
||||||
"number of shipment": "nombre de livraisons",
|
|
||||||
"cancel": "annuler",
|
|
||||||
"create form": "créer un formulaire de contrat",
|
|
||||||
"create productor": "créer le/la producteur·trice",
|
|
||||||
"edit productor": "modifier le/la producteur·trice",
|
|
||||||
"remove productor": "supprimer le/la producteur·trice",
|
|
||||||
"home": "accueil",
|
|
||||||
"dashboard": "tableau de bord",
|
|
||||||
"filter by name": "filtrer par nom",
|
|
||||||
"filter by type": "filtrer par type",
|
|
||||||
"address": "adresse",
|
|
||||||
"payment methods": "méthodes de paiement",
|
|
||||||
"type": "type",
|
|
||||||
"cheque": "chèque",
|
|
||||||
"transfer": "virement",
|
|
||||||
"order name": "ordre du chèque",
|
|
||||||
"productor name": "nom du producteur·trice",
|
|
||||||
"productor type": "type du producteur·trice",
|
|
||||||
"productor address": "adresse du producteur·trice",
|
|
||||||
"productor payment": "méthodes de paiement du producteur·trice",
|
|
||||||
"priceKg": "prix au kilo",
|
|
||||||
"quantity": "quantité",
|
|
||||||
"quantity unit": "unité de quantité",
|
|
||||||
"unit": "unité de vente",
|
|
||||||
"price": "prix",
|
|
||||||
"total price": "prix total",
|
|
||||||
"create product": "créer le produit",
|
|
||||||
"informations": "informations",
|
|
||||||
"remove product": "supprimer le produit",
|
|
||||||
"edit product": "modifier le produit",
|
|
||||||
"shipment name": "nom de la livraison",
|
|
||||||
"shipment date": "date de la livraison",
|
|
||||||
"shipments": "livraisons",
|
|
||||||
"shipment": "livraison",
|
|
||||||
"shipment products": "produits pour la livraison",
|
|
||||||
"shipment form": "formulaire lié a la livraison",
|
|
||||||
"minimum shipment value": "valeur minimum d'une livraison (€)",
|
|
||||||
"shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "il est nécessaire de configurer les produits pour la livraison uniquement si il y a des produits occasionnels (laisser vide si tous les produits sont récurents).",
|
|
||||||
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "les produits récurrents sont pour toutes les livraisons, les produits occasionnels sont pour une livraison particulière (voir formulaire de création de livraison).",
|
|
||||||
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "certains contrats nécessitent une valeur minimum par livraison. Ce champ peut être ignoré s’il ne s’applique pas à votre contrat.",
|
|
||||||
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "en cochant cette option le formulaire sera accessible publiquement sur la page d'accueil, cochez cette option uniquement si tout est prêt avec votre formulaire.",
|
|
||||||
"contracts": "contrats",
|
|
||||||
"hidden": "caché",
|
|
||||||
"visible": "visible",
|
|
||||||
"minimum price for this shipment should be at least": "le prix minimum d'une livraison doit être au moins de",
|
|
||||||
"there is": "il y a",
|
|
||||||
"for this contract": "pour ce contrat.",
|
|
||||||
"remove shipment": "supprimer la livraison",
|
|
||||||
"productors": "producteur·trices",
|
|
||||||
"products": "produits",
|
|
||||||
"templates": "modèles",
|
|
||||||
"users": "utilisateur·trices",
|
|
||||||
"forms": "formulaires de contrat",
|
|
||||||
"form": "formulaire de contrat",
|
|
||||||
"select a form": "selectionnez un formulaire",
|
|
||||||
"download contracts": "télécharger les contrats",
|
|
||||||
"all contracts": "tous les contrats",
|
|
||||||
"remove contract": "supprimer le contrat",
|
|
||||||
"download contract": "télécharger le contrat",
|
|
||||||
"fill contract online": "remplir le contrat en ligne",
|
|
||||||
"download base template to print": "télécharger le contrat à remplir sur papier",
|
|
||||||
"by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.",
|
|
||||||
"edit user": "modifier l'utilisateur·trice",
|
|
||||||
"remove user": "supprimer l'utilisateur·trice",
|
|
||||||
"max cheque number": "numbre maximum de cheques possible",
|
|
||||||
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
|
|
||||||
"all forms": "tous les formulaires de contrat",
|
|
||||||
"create new form": "créer un nouveau formulaire de contrat",
|
|
||||||
"actions": "actions",
|
"actions": "actions",
|
||||||
|
"add all products linked to this productor in the products section": "ajoutez vos produits liés au/à la producteur·trice dans la section \"Produits\".",
|
||||||
|
"address": "adresse",
|
||||||
|
"all contracts": "tous les contrats",
|
||||||
|
"all forms": "tous les formulaires de contrat",
|
||||||
"all productors": "tous les producteur·trices",
|
"all productors": "tous les producteur·trices",
|
||||||
"all products": "tous les produits",
|
"all products": "tous les produits",
|
||||||
"all shipments": "toutes les livraisons",
|
|
||||||
"all referers": "tous les référent·es",
|
"all referers": "tous les référent·es",
|
||||||
"is required": "est requis·e",
|
"all shipments": "toutes les livraisons",
|
||||||
"a name": "un nom",
|
|
||||||
"a season": "une saison",
|
|
||||||
"a start date": "une date de début",
|
|
||||||
"a end date": "une date de fin",
|
|
||||||
"a productor": "un(e) producteur·trice",
|
|
||||||
"a referer": "un(e) référent·e",
|
|
||||||
"a phone": "un numéro de téléphone",
|
|
||||||
"a fistname": "un prénom",
|
|
||||||
"a lastname": "un nom",
|
|
||||||
"a email": "une adresse email",
|
|
||||||
"a price or priceKg": "un prix ou un prix au kilo",
|
|
||||||
"a address": "une adresse",
|
|
||||||
"a type": "un type",
|
|
||||||
"a form": "un formulaire de contrat",
|
|
||||||
"a price": "un prix",
|
|
||||||
"a priceKg": "un prix au kilo",
|
|
||||||
"a quantity": "une quantité",
|
|
||||||
"a product": "un produit",
|
|
||||||
"a quantity unit": "une unité de quantité",
|
|
||||||
"a payment method": "une méthode de paiement",
|
|
||||||
"a sell unit": "une unité de vente",
|
|
||||||
"sell unit": "unité de vente",
|
|
||||||
"product": "produit",
|
|
||||||
"a shipment": "une livraison",
|
|
||||||
"the products": "les produits",
|
|
||||||
"the shipments": "les livraisons",
|
|
||||||
"link to the section": "lien vers la section : {{section}}",
|
|
||||||
"to add a use the": "pour ajouter {{section}} utilisez le bouton",
|
|
||||||
"to edit a use the": "pour éditer {{section}} utilisez le bouton",
|
|
||||||
"to delete a use the": "pour supprimer {{section}} utilisez le bouton",
|
|
||||||
"button in top right of the page": "en haut à droite de la page {{section}}.",
|
|
||||||
"button in front of the line you want to edit": "en face de la ligne que vous souhaitez éditer. (dans la colonne actions).",
|
|
||||||
"button in front of the line you want to delete": "en face de la ligne que vous souhaitez supprimer. (dans la colonne actions).",
|
|
||||||
"glossary": "glossaire",
|
|
||||||
"logout": "se déconnecter",
|
|
||||||
"start to create a productor in the productors section": "commencez par créer un(e) producteur·trice dans la section \"Producteur·trices\".",
|
|
||||||
"add all products linked to this productor in the products section": "ajoutez vos produits liés au/à la producteur·trice dans la section \"Produits\".",
|
|
||||||
"create your contract form, it will create a form in the home page (accessible to users)": "créez votre formulaire de contrat dans la section \"Formulaire de contrat\". Ajouter une entrée dans cette section ajoutera un formulaire dans la page d'accueil.",
|
|
||||||
"export contracts": "Télécharger les contrats",
|
|
||||||
"download recap": "Télécharger le récapitulatif",
|
|
||||||
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "dans cette page vous pouvez voir tous les contrats, vous pouvez supprimer un contrat en doublon, ou télécharger uniquement un contrat.",
|
|
||||||
"create shipments for your contract form": "créez les livraisons pour votre contrat",
|
|
||||||
"creation order": "ordre de création",
|
|
||||||
"in the same corner you can download a recap by clicking on the button": "au même endroit vous pouvez exporter votre récapitulatif (format odt) à vérifier et transmettre au producteur en cliquant sur le bouton",
|
|
||||||
"to export contracts submissions before sending to the productor go to the contracts section": "pour exporter les contrats avant de les envoyer aux producteurs allez dans la section \"contrats\".",
|
|
||||||
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "une fois tous les contrats récupérés vous pouvez supprimer le formulaire (pour éviter les nouvelles demandes de contrat et pour cacher le formulaire de la page principale).",
|
|
||||||
"you can download all contracts for your form using the export all": "vous pouvez télécharger tous les contrats de votre formulaire en utilisant le bouton",
|
|
||||||
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "le tableau de bord est visible uniquement pour les référents, vous pouvez créer votre producteur, vos produits, vos formulaires de contrat et vos livraisons.",
|
|
||||||
"is defined by": "est defini par",
|
|
||||||
"a product type define the way it will be organized on the final contract form (showed to users) it can be reccurent or occassional. Recurrent products will be set for all shipments if selected by user, Occasional products can be choosen for each shipments": "un type de produit définit la manière dont un produit va être présenté aux amapiens dans le formulaire de contrat. Il peut être récurrent ou occasionnel. Un produit récurrent si selectionné sera compté pour toutes les livraisons. Un produit occasionnel sera facultatif pour chaques livraison (l'amapien devra selectionner la quantité voulue pour chaque livraisons).",
|
|
||||||
"and/or": "et/ou",
|
|
||||||
"form name recommandation": "recommandation : Contrat <contract-type> (Exemple : Contrat Porc-Agneau)",
|
|
||||||
"submit contract": "envoyer le contrat",
|
|
||||||
"example in user forms": "exemple dans le formulaire à destination des amapiens",
|
|
||||||
"occasional product": "produit occasionnel",
|
|
||||||
"recurrent product": "produit récurrent",
|
|
||||||
"with grams as product unit selected": "avec \"grammes\" selectionné pour l'unité de produit",
|
|
||||||
"product example": "exemple de produit",
|
|
||||||
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "les méthodes de paiement sont définies par producteurs. À la fin du formulaire de contrat l'amapien pourra séléctionner sa méthode de paiement parmis celles que vous avez ajoutés.",
|
|
||||||
"with cheque and transfer": "avec chèques et virements configuré pour le producteur",
|
|
||||||
"mililiter": "mililitres (ml)",
|
|
||||||
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "ce champ est optionnel dans la configuration d'un produit, il représente la quantité d'un produit (poids d'une tranche de foie, poids d'un panier, taille d'un bocal...). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens.",
|
|
||||||
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "ce champs est optionnel dans la configuation d'un produit, il représente l'unité de mesure associée à la quantité d'un produit (g, kg, ml, L). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens à coté de la quantité du produit.",
|
|
||||||
"with 150 set as quantity and g as quantity unit in product": "avec \"150\" en quantité de produit et \"grammes\" selectionné dans l'unité de quantité du produit",
|
|
||||||
"all shipments should be recreated for each form creation": "les livraisons étant liées à un formulaire elles doivent être recréés pour chaque nouveau formulaire.",
|
"all shipments should be recreated for each form creation": "les livraisons étant liées à un formulaire elles doivent être recréés pour chaque nouveau formulaire.",
|
||||||
"a productor can be edited if its informations change, it should not be recreated for each contracts": "un(e) producteur·trice peut être édité si ses informations changent, il/elle ne doit pas être recréé pour chaque nouveau contrat.",
|
"all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat.",
|
||||||
"a product can be edited if its informations change, it should not be recreated for each contracts": "un produit peut être édité si ses informations changent, il ne doit pas être recréé pour chaque nouveau formulaire de contrat.",
|
"and/or": "et/ou",
|
||||||
"a new contract form should be created for each new season, do not edit a previous contract and change it's values (for history purpose)": "un formulaire de contrat doit être créé pour chaque nouvelle saison, pour des raison d'historique, n'éditez pas un formulaire de contrat passé pour une nouvelle saison, recréez en un nouveau.",
|
"are you sure you want to delete": "êtes vous sûr de vouloir supprimer",
|
||||||
"grams": "grammes (g)",
|
"button in front of the line you want to delete": "en face de la ligne que vous souhaitez supprimer. (dans la colonne actions).",
|
||||||
"kilo": "kilogrammes (kg)",
|
"button in front of the line you want to edit": "en face de la ligne que vous souhaitez éditer. (dans la colonne actions).",
|
||||||
"liter": "litres (L)",
|
"button in top right of the page": "en haut à droite de la page {{section}}.",
|
||||||
"success": "succès",
|
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form": "en cochant cette option le formulaire sera accessible publiquement sur la page d'accueil, cochez cette option uniquement si tout est prêt avec votre formulaire.",
|
||||||
"success edit": "{{entity}} correctement édité",
|
"by selecting a form here you can download all contracts of your form": "en selectionnant un formulaire, vous téléchargez tous les contrats pour un formulaire donné.",
|
||||||
"success create": "{{entity}} correctement créé",
|
"can be empty default to 3": "optionnel, la valeur par défaut est à 3 cheques",
|
||||||
"success delete": "{{entity}} correctement supprimé",
|
"cancel": "annuler",
|
||||||
|
"cheque": "chèque",
|
||||||
|
"cheque id": "identifiant du chèque",
|
||||||
|
"cheque quantity": "quantité de chèques (pour le paiement en plusieurs fois)",
|
||||||
|
"cheque value": "valeur du chèque",
|
||||||
|
"choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).",
|
||||||
|
"contract": "contrat",
|
||||||
|
"contract season": "saison du contrat",
|
||||||
|
"contract season recommandation": "recommandation : <Saison>-<année> (Exemple: Hiver-2025), si un formulaire est déjà créé pour la saison, reprenez son nom de saison si possible.",
|
||||||
|
"contracts": "contrats",
|
||||||
|
"create form": "créer un formulaire de contrat",
|
||||||
|
"create new form": "créer un nouveau formulaire de contrat",
|
||||||
|
"create product": "créer le produit",
|
||||||
|
"create productor": "créer le/la producteur·trice",
|
||||||
|
"create shipment": "créer la livraison",
|
||||||
|
"create shipments for your contract form": "créez les livraisons pour votre contrat",
|
||||||
|
"create user": "créer l'utilisateur·trice",
|
||||||
|
"create your contract form, it will create a form in the home page (accessible to users)": "créez votre formulaire de contrat dans la section \"Formulaire de contrat\". Ajouter une entrée dans cette section ajoutera un formulaire dans la page d'accueil.",
|
||||||
|
"creation order": "ordre de création",
|
||||||
|
"dashboard": "tableau de bord",
|
||||||
|
"dashboard is for referers only, with this dashboard you can create productors, products, forms and shipments": "le tableau de bord est visible uniquement pour les référents, vous pouvez créer votre producteur, vos produits, vos formulaires de contrat et vos livraisons.",
|
||||||
|
"delete": "supprimer",
|
||||||
|
"delete entity": "supprimer le/la {{entity}}",
|
||||||
|
"download base template to print": "télécharger le contrat à remplir sur papier",
|
||||||
|
"download contract": "télécharger le contrat",
|
||||||
|
"download contracts": "télécharger les contrats",
|
||||||
|
"download recap": "Télécharger le récapitulatif",
|
||||||
|
"edit form": "modifier le formulaire de contrat",
|
||||||
|
"edit product": "modifier le produit",
|
||||||
|
"edit productor": "modifier le/la producteur·trice",
|
||||||
|
"edit shipment": "modifier la livraison",
|
||||||
|
"edit user": "modifier l'utilisateur·trice",
|
||||||
|
"end": "fin",
|
||||||
|
"end date": "date de fin",
|
||||||
|
"enter cheque quantity": "entrez la quantité de chèques",
|
||||||
|
"enter cheque value": "entrez la valeur du chèque",
|
||||||
|
"enter payment method": "sélectionnez votre méthode de paiement",
|
||||||
|
"enter quantity": "entrez la quantité",
|
||||||
"error": "erreur",
|
"error": "erreur",
|
||||||
"error edit": "erreur pendant l'édition {{entity}}",
|
|
||||||
"error create": "erreur pendant la création {{entity}}",
|
"error create": "erreur pendant la création {{entity}}",
|
||||||
"error delete": "erreur pendant la suppression {{entity}}",
|
"error delete": "erreur pendant la suppression {{entity}}",
|
||||||
"of the user": "de l'utilisateur·trice",
|
"error edit": "erreur pendant l'édition {{entity}}",
|
||||||
|
"example in user forms": "exemple dans le formulaire à destination des amapiens",
|
||||||
|
"export contracts": "Télécharger les contrats",
|
||||||
|
"fill contract online": "remplir le contrat en ligne",
|
||||||
|
"filter by form": "filtrer par formulaire",
|
||||||
|
"filter by name": "filtrer par nom",
|
||||||
|
"filter by productor": "filtrer par producteur·trice",
|
||||||
|
"filter by season": "filtrer par saisons",
|
||||||
|
"filter by type": "filtrer par type",
|
||||||
|
"for this contract": "pour ce contrat.",
|
||||||
|
"for transfer method contact your referer or productor": "pour mettre en place le virement automatique, contactez votre référent ou le producteur.",
|
||||||
|
"form": "formulaire de contrat",
|
||||||
|
"form name": "nom du formulaire de contrat",
|
||||||
|
"form name recommandation": "recommandation : Contrat <contract-type> (Exemple : Contrat Porc-Agneau)",
|
||||||
|
"forms": "formulaires de contrat",
|
||||||
|
"glossary": "glossaire",
|
||||||
|
"grams": "grammes (g)",
|
||||||
|
"help": "aide",
|
||||||
|
"hidden": "caché",
|
||||||
|
"home": "accueil",
|
||||||
|
"how to use dashboard": "comment utiliser le tableau de bord",
|
||||||
|
"in": "en",
|
||||||
|
"in the same corner you can download a recap by clicking on the button": "au même endroit vous pouvez exporter votre récapitulatif (format odt) à vérifier et transmettre au producteur en cliquant sur le bouton",
|
||||||
|
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract": "dans cette page vous pouvez voir tous les contrats, vous pouvez supprimer un contrat en doublon, ou télécharger uniquement un contrat.",
|
||||||
|
"informations": "informations",
|
||||||
|
"is defined by": "est defini par",
|
||||||
|
"is required": "est requis·e",
|
||||||
|
"kilo": "kilogrammes (kg)",
|
||||||
|
"link to the section": "lien vers la section : {{section}}",
|
||||||
|
"liter": "litres (L)",
|
||||||
|
"login with keycloak": "se connecter avec keycloak",
|
||||||
|
"logout": "se déconnecter",
|
||||||
|
"max cheque number": "numbre maximum de cheques possible",
|
||||||
|
"mililiter": "mililitres (ml)",
|
||||||
|
"minimum price for this shipment should be at least": "le prix minimum d'une livraison doit être au moins de",
|
||||||
|
"minimum shipment value": "valeur minimum d'une livraison (€)",
|
||||||
|
"name": "nom",
|
||||||
|
"nothing found": "rien à afficher",
|
||||||
|
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre 1 et 3, entrez également les identifiants des chèques utilisés.",
|
||||||
|
"number of shipment": "nombre de livraisons",
|
||||||
|
"occasional": "occasionnel",
|
||||||
|
"occasional product": "produit occasionnel",
|
||||||
|
"occasional products": "produits occasionnels par livraison",
|
||||||
|
"of the contract": "du contrat",
|
||||||
"of the form": "du formulaire",
|
"of the form": "du formulaire",
|
||||||
"of the product": "du produit",
|
"of the product": "du produit",
|
||||||
"of the productor": "du producteur·trice",
|
"of the productor": "du producteur·trice",
|
||||||
"of the shipment": "de la livraison",
|
"of the shipment": "de la livraison",
|
||||||
"of the contract": "du contrat",
|
"of the user": "de l'utilisateur·trice",
|
||||||
"login with keycloak": "se connecter avec keycloak",
|
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page": "une fois tous les contrats récupérés vous pouvez supprimer le formulaire (pour éviter les nouvelles demandes de contrat et pour cacher le formulaire de la page principale).",
|
||||||
"there is no contract for now": "il n'y a pas de contrats pour le moment.",
|
"order name": "ordre du chèque",
|
||||||
"for transfer method contact your referer or productor": "pour mettre en place le virement automatique, contactez votre référent ou le producteur.",
|
|
||||||
"cheque quantity": "quantité de chèques (pour le paiement en plusieurs fois)",
|
|
||||||
"enter cheque quantity": "entrez la quantité de chèques",
|
|
||||||
"cheque id": "identifiant du chèque",
|
|
||||||
"cheque value": "valeur du chèque",
|
|
||||||
"enter cheque value": "entrez la valeur du chèque",
|
|
||||||
"enter payment method": "sélectionnez votre méthode de paiement",
|
|
||||||
"number of cheques between 1 and 3 cheques also enter your cheques identifiers, value is calculated automatically": "nombre de chèques entre 1 et 3, entrez également les identifiants des chèques utilisés.",
|
|
||||||
"payment method": "méthode de paiement",
|
"payment method": "méthode de paiement",
|
||||||
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter.",
|
"payment methods": "méthodes de paiement",
|
||||||
"session expired": "session expirée",
|
"payment methods are defined for a productor. At the end of a form a section payment method let the user select his prefered payment method": "les méthodes de paiement sont définies par producteurs. À la fin du formulaire de contrat l'amapien pourra séléctionner sa méthode de paiement parmis celles que vous avez ajoutés.",
|
||||||
"user not allowed": "utilisateur non authorisé",
|
"piece": "pièce",
|
||||||
|
"price": "prix",
|
||||||
|
"priceKg": "prix au kilo",
|
||||||
|
"product": "produit",
|
||||||
|
"product example": "exemple de produit",
|
||||||
|
"product name": "nom du produit",
|
||||||
|
"product price": "prix du produit",
|
||||||
|
"product price kg": "prix du produit au Kilo",
|
||||||
|
"product quantity": "quantité du produit",
|
||||||
|
"product quantity unit": "unité de quantité du produit",
|
||||||
|
"product type": "type de produit",
|
||||||
|
"product unit": "unité de vente du produit",
|
||||||
|
"productor": "producteur·trice",
|
||||||
|
"productor address": "adresse du producteur·trice",
|
||||||
|
"productor name": "nom du producteur·trice",
|
||||||
|
"productor payment": "méthodes de paiement du producteur·trice",
|
||||||
|
"productor type": "type du producteur·trice",
|
||||||
|
"productors": "producteur·trices",
|
||||||
|
"products": "produits",
|
||||||
|
"quantity": "quantité",
|
||||||
|
"quantity unit": "unité de quantité",
|
||||||
|
"recurrent": "récurent",
|
||||||
|
"recurrent product": "produit récurrent",
|
||||||
|
"recurrent product is for all shipments, occasional product is for a specific shipment (see shipment form)": "les produits récurrents sont pour toutes les livraisons, les produits occasionnels sont pour une livraison particulière (voir formulaire de création de livraison).",
|
||||||
|
"recurrent products": "produits récurrents",
|
||||||
|
"referer": "référent·e",
|
||||||
|
"remove contract": "supprimer le contrat",
|
||||||
|
"remove form": "supprimer un formulaire de contrat",
|
||||||
|
"remove product": "supprimer le produit",
|
||||||
|
"remove productor": "supprimer le/la producteur·trice",
|
||||||
|
"remove shipment": "supprimer la livraison",
|
||||||
|
"remove user": "supprimer l'utilisateur·trice",
|
||||||
"roles": "roles",
|
"roles": "roles",
|
||||||
"your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.",
|
"season": "saison",
|
||||||
"choose payment method": "choisissez votre méthode de paiement (vous n'avez pas à payer tout de suite, uniquement renseigner comment vous souhaitez régler votre commande).",
|
"select a form": "selectionnez un formulaire",
|
||||||
|
"select products per shipment": "sélectionnez les produits pour chaque livraison.",
|
||||||
|
"sell unit": "unité de vente",
|
||||||
|
"session expired": "session expirée",
|
||||||
|
"shipment": "livraison",
|
||||||
|
"shipment date": "date de la livraison",
|
||||||
|
"shipment form": "formulaire lié a la livraison",
|
||||||
|
"shipment name": "nom de la livraison",
|
||||||
|
"shipment products": "produits pour la livraison",
|
||||||
|
"shipment products is necessary only for occasional products (if all products are recurrent leave empty)": "il est nécessaire de configurer les produits pour la livraison uniquement si il y a des produits occasionnels (laisser vide si tous les produits sont récurents).",
|
||||||
|
"shipments": "livraisons",
|
||||||
|
"some contracts require a minimum value per shipment, ignore this field if it's not the case": "certains contrats nécessitent une valeur minimum par livraison. Ce champ peut être ignoré s’il ne s’applique pas à votre contrat.",
|
||||||
|
"start": "début",
|
||||||
|
"start date": "date de début",
|
||||||
|
"start to create a productor in the productors section": "commencez par créer un(e) producteur·trice dans la section \"Producteur·trices\".",
|
||||||
|
"submit": "envoyer",
|
||||||
|
"submit contract": "envoyer le contrat",
|
||||||
|
"success": "succès",
|
||||||
|
"success create": "{{entity}} correctement créé",
|
||||||
|
"success delete": "{{entity}} correctement supprimé",
|
||||||
|
"success edit": "{{entity}} correctement édité",
|
||||||
|
"templates": "modèles",
|
||||||
"the product unit will be assigned to the quantity requested in the form": "l'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.",
|
"the product unit will be assigned to the quantity requested in the form": "l'unité de vente du produit définit l'unité associée à la quantité demandée dans le formulaire des amapiens.",
|
||||||
"all theses informations are for contract generation": "ces informations sont nécessaires pour la génération de contrat."
|
"the products": "les produits",
|
||||||
|
"the shipments": "les livraisons",
|
||||||
|
"there is": "il y a",
|
||||||
|
"there is no contract for now": "il n'y a pas de contrats pour le moment.",
|
||||||
|
"this field is also optionnal if a product have a quantity you can select the correct unit (metric system). It will be shown next to product quantity inside the form": "ce champs est optionnel dans la configuation d'un produit, il représente l'unité de mesure associée à la quantité d'un produit (g, kg, ml, L). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens à coté de la quantité du produit.",
|
||||||
|
"this field is optionnal a product can have a quantity if configured inside the product it will be shown inside the form": "ce champ est optionnel dans la configuration d'un produit, il représente la quantité d'un produit (poids d'une tranche de foie, poids d'un panier, taille d'un bocal...). Si ce champs est renseigné il sera affiché dans le formulaire à destination des amapiens.",
|
||||||
|
"this will also delete": "cette action supprimera aussi",
|
||||||
|
"to add a use the": "pour ajouter {{section}} utilisez le bouton",
|
||||||
|
"to delete a use the": "pour supprimer {{section}} utilisez le bouton",
|
||||||
|
"to edit a use the": "pour éditer {{section}} utilisez le bouton",
|
||||||
|
"to export contracts submissions before sending to the productor go to the contracts section": "pour exporter les contrats avant de les envoyer aux producteurs allez dans la section \"contrats\".",
|
||||||
|
"total price": "prix total",
|
||||||
|
"transfer": "virement",
|
||||||
|
"type": "type",
|
||||||
|
"unit": "unité de vente",
|
||||||
|
"user": "utilisateur·trice",
|
||||||
|
"user not allowed": "utilisateur non authorisé",
|
||||||
|
"users": "utilisateur·trices",
|
||||||
|
"visible": "visible",
|
||||||
|
"with 150 set as quantity and g as quantity unit in product": "avec \"150\" en quantité de produit et \"grammes\" selectionné dans l'unité de quantité du produit",
|
||||||
|
"with cheque and transfer": "avec chèques et virements configuré pour le producteur",
|
||||||
|
"with grams as product unit selected": "avec \"grammes\" selectionné pour l'unité de produit",
|
||||||
|
"you can download all contracts for your form using the export all": "vous pouvez télécharger tous les contrats de votre formulaire en utilisant le bouton",
|
||||||
|
"your keycloak user has no roles, please contact your administrator": "votre utilisateur keycloak n'a pas de roles configurés, contactez votre administrateur.",
|
||||||
|
"your selection in this category will apply for all shipments": "votre sélection sera appliquée pour chaque livraisons (Exemple: Pour 6 livraisons, le produits sera compté 6 fois : une fois par livraison).",
|
||||||
|
"your session has expired please log in again": "votre session a expiré veuillez vous reconnecter."
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,9 @@ export function ContractModal({ opened, onClose, handleSubmit }: ContractModalPr
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formSelect = useMemo(() => {
|
const formSelect = useMemo(() => {
|
||||||
|
if (!allForms) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return allForms?.map((form) => ({
|
return allForms?.map((form) => ({
|
||||||
value: String(form.id),
|
value: String(form.id),
|
||||||
label: `${form.season} ${form.name}`,
|
label: `${form.season} ${form.name}`,
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
|||||||
import { type Contract } from "@/services/resources/contracts";
|
import { type Contract } from "@/services/resources/contracts";
|
||||||
import { IconDownload, IconX } from "@tabler/icons-react";
|
import { IconDownload, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useDeleteContract, useGetContractFile } from "@/services/api";
|
import { useGetContractFile } from "@/services/api";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
export type ContractRowProps = {
|
export type ContractRowProps = {
|
||||||
contract: Contract;
|
contract: Contract;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ContractRow({ contract }: ContractRowProps) {
|
export default function ContractRow({ contract }: ContractRowProps) {
|
||||||
const deleteMutation = useDeleteContract();
|
|
||||||
const getContractMutation = useGetContractFile();
|
const getContractMutation = useGetContractFile();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleDownload = useCallback(async () => {
|
const handleDownload = useCallback(async () => {
|
||||||
getContractMutation.mutateAsync(contract.id);
|
getContractMutation.mutateAsync(contract.id);
|
||||||
@@ -29,12 +30,10 @@ export default function ContractRow({ contract }: ContractRowProps) {
|
|||||||
{contract.cheque_quantity > 0 && contract.cheque_quantity} {contract.payment_method}
|
{contract.cheque_quantity > 0 && contract.cheque_quantity} {contract.payment_method}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{
|
{`${Intl.NumberFormat("fr-FR", {
|
||||||
`${Intl.NumberFormat("fr-FR", {
|
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "EUR",
|
currency: "EUR",
|
||||||
}).format(contract.total_price)}`
|
}).format(contract.total_price)}`}
|
||||||
}
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label={t("download contract", { capfirst: true })}>
|
<Tooltip label={t("download contract", { capfirst: true })}>
|
||||||
@@ -54,8 +53,9 @@ export default function ContractRow({ contract }: ContractRowProps) {
|
|||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(contract.id);
|
e.stopPropagation();
|
||||||
|
navigate(`/dashboard/contracts/${contract.id}/delete`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
78
frontend/src/components/DeleteModal/index.tsx
Normal file
78
frontend/src/components/DeleteModal/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { t } from "@/config/i18n";
|
||||||
|
import { useGetDeleteDependencies } from "@/services/api";
|
||||||
|
import { Button, Group, List, Modal, Text, type ModalBaseProps } from "@mantine/core";
|
||||||
|
import { IconCancel, IconCheck } from "@tabler/icons-react";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
export type DeleteModalProps = ModalBaseProps & {
|
||||||
|
handleSubmit: (id: number) => void;
|
||||||
|
entityType: string;
|
||||||
|
entity?: { name: string; id: number } | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DeleteModal({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
handleSubmit,
|
||||||
|
entityType,
|
||||||
|
entity,
|
||||||
|
}: DeleteModalProps) {
|
||||||
|
if (!entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { data: deleteDependencies } = useGetDeleteDependencies(entityType, entity.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("delete entity", { capfirst: true, entity: t(entityType) })}
|
||||||
|
>
|
||||||
|
<Text>{`${t("are you sure you want to delete", { capfirst: true })} : "${entity.name}"`}</Text>
|
||||||
|
{deleteDependencies && deleteDependencies.length > 0 ? (
|
||||||
|
<Text>{`${t("this will also delete", { capfirst: true })} :`}</Text>
|
||||||
|
) : null}
|
||||||
|
{
|
||||||
|
<List>
|
||||||
|
{deleteDependencies?.map((dependency) => (
|
||||||
|
<List.Item key={dependency.id}>
|
||||||
|
{dependency.type === "contract" ? (
|
||||||
|
`${t(dependency.type, { capfirst: true })} - ${dependency.name}`
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to={`/dashboard/${dependency.type}s/${dependency.id}/edit`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{`${t(dependency.type, { capfirst: true })} - ${dependency.name}`}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
}
|
||||||
|
<Group mt="sm" justify="space-between">
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
color="red"
|
||||||
|
aria-label={t("cancel", { capfirst: true })}
|
||||||
|
leftSection={<IconCancel />}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{t("cancel", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
aria-label={t("delete entity", { capfirst: true, entity: t(entityType) })}
|
||||||
|
leftSection={<IconCheck />}
|
||||||
|
onClick={() => {
|
||||||
|
handleSubmit(entity.id);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("delete", { capfirst: true })}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,26 +10,22 @@ export type FormCardProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function FormCard({ form }: FormCardProps) {
|
export function FormCard({ form }: FormCardProps) {
|
||||||
const contractBaseTemplate = useGetContractFileTemplate()
|
const contractBaseTemplate = useGetContractFileTemplate();
|
||||||
return (
|
return (
|
||||||
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
<Paper shadow="xl" p="xl" miw={{ base: "100vw", md: "25vw", lg: "20vw" }}>
|
||||||
<Group justify="start" mb="md">
|
<Group justify="start" mb="md">
|
||||||
<Tooltip
|
<Tooltip label={t("download base template to print")}>
|
||||||
label={t("download base template to print")}
|
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
aria-label={t("download base template to print")}
|
aria-label={t("download base template to print")}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await contractBaseTemplate.mutateAsync(form.id)
|
await contractBaseTemplate.mutateAsync(form.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconDownload />
|
<IconDownload />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip label={t("fill contract online")}>
|
||||||
label={t("fill contract online")}
|
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
aria-label={t("fill contract online")}
|
aria-label={t("fill contract online")}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
|||||||
productor_id: currentForm?.productor?.id.toString() ?? "",
|
productor_id: currentForm?.productor?.id.toString() ?? "",
|
||||||
referer_id: currentForm?.referer?.id.toString() ?? "",
|
referer_id: currentForm?.referer?.id.toString() ?? "",
|
||||||
minimum_shipment_value: currentForm?.minimum_shipment_value ?? null,
|
minimum_shipment_value: currentForm?.minimum_shipment_value ?? null,
|
||||||
visible: currentForm?.visible ?? false
|
visible: currentForm?.visible ?? false,
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
name: (value) =>
|
name: (value) =>
|
||||||
@@ -53,6 +53,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
|||||||
});
|
});
|
||||||
|
|
||||||
const usersSelect = useMemo(() => {
|
const usersSelect = useMemo(() => {
|
||||||
|
if (!users) return [];
|
||||||
return users?.map((user) => ({
|
return users?.map((user) => ({
|
||||||
value: String(user.id),
|
value: String(user.id),
|
||||||
label: `${user.name}`,
|
label: `${user.name}`,
|
||||||
@@ -60,6 +61,7 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
|||||||
}, [users]);
|
}, [users]);
|
||||||
|
|
||||||
const productorsSelect = useMemo(() => {
|
const productorsSelect = useMemo(() => {
|
||||||
|
if (!productors) return [];
|
||||||
return productors?.map((prod) => ({
|
return productors?.map((prod) => ({
|
||||||
value: String(prod.id),
|
value: String(prod.id),
|
||||||
label: `${prod.name}`,
|
label: `${prod.name}`,
|
||||||
@@ -138,9 +140,13 @@ export default function FormModal({ opened, onClose, currentForm, handleSubmit }
|
|||||||
radius="sm"
|
radius="sm"
|
||||||
{...form.getInputProps("minimum_shipment_value")}
|
{...form.getInputProps("minimum_shipment_value")}
|
||||||
/>
|
/>
|
||||||
<Checkbox mt="lg"
|
<Checkbox
|
||||||
|
mt="lg"
|
||||||
label={t("visible", { capfirst: true })}
|
label={t("visible", { capfirst: true })}
|
||||||
description={t("by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form", {capfirst: true})}
|
description={t(
|
||||||
|
"by checking this option the form will be accessible publicly on the home page, only check it if everything is fine with your form",
|
||||||
|
{ capfirst: true },
|
||||||
|
)}
|
||||||
{...form.getInputProps("visible", { type: "checkbox" })}
|
{...form.getInputProps("visible", { type: "checkbox" })}
|
||||||
/>
|
/>
|
||||||
<Group mt="sm" justify="space-between">
|
<Group mt="sm" justify="space-between">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useDeleteForm } from "@/services/api";
|
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import type { Form } from "@/services/resources/forms";
|
import type { Form } from "@/services/resources/forms";
|
||||||
@@ -11,16 +10,15 @@ export type FormRowProps = {
|
|||||||
|
|
||||||
export default function FormRow({ form }: FormRowProps) {
|
export default function FormRow({ form }: FormRowProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteForm();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Tr key={form.id}>
|
<Table.Tr key={form.id}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{form.visible ?
|
{form.visible ? (
|
||||||
<Badge color="green">{t("visible", {capfirst: true})}</Badge> :
|
<Badge color="green">{t("visible", { capfirst: true })}</Badge>
|
||||||
|
) : (
|
||||||
<Badge color="red">{t("hidden", { capfirst: true })}</Badge>
|
<Badge color="red">{t("hidden", { capfirst: true })}</Badge>
|
||||||
}
|
)}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>{form.name}</Table.Td>
|
<Table.Td>{form.name}</Table.Td>
|
||||||
<Table.Td>{form.season}</Table.Td>
|
<Table.Td>{form.season}</Table.Td>
|
||||||
@@ -29,7 +27,7 @@ export default function FormRow({ form }: FormRowProps) {
|
|||||||
<Table.Td>{form.productor.name}</Table.Td>
|
<Table.Td>{form.productor.name}</Table.Td>
|
||||||
<Table.Td>{form.referer.name}</Table.Td>
|
<Table.Td>{form.referer.name}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label={t("edit productor", { capfirst: true })}>
|
<Tooltip label={t("edit form", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
@@ -43,13 +41,16 @@ export default function FormRow({ form }: FormRowProps) {
|
|||||||
<IconEdit />
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove productor", { capfirst: true })}>
|
<Tooltip label={t("remove form", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(form.id);
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/dashboard/forms/${form.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import "./index.css";
|
|||||||
import { Group, Loader } from "@mantine/core";
|
import { Group, Loader } from "@mantine/core";
|
||||||
import { Config } from "@/config/config";
|
import { Config } from "@/config/config";
|
||||||
import { useAuth } from "@/services/auth/AuthProvider";
|
import { useAuth } from "@/services/auth/AuthProvider";
|
||||||
|
import { useMediaQuery } from "@mantine/hooks";
|
||||||
|
import { IconHome, IconLogin, IconLogout, IconSettings } from "@tabler/icons-react";
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const { loggedUser: user, isLoading } = useAuth();
|
const { loggedUser: user, isLoading } = useAuth();
|
||||||
|
const isPhone = useMediaQuery("(max-width: 760px");
|
||||||
|
|
||||||
if (!user && isLoading) {
|
if (!user && isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -20,11 +23,11 @@ export function Navbar() {
|
|||||||
<nav>
|
<nav>
|
||||||
<Group>
|
<Group>
|
||||||
<NavLink className={"navLink"} aria-label={t("home")} to="/">
|
<NavLink className={"navLink"} aria-label={t("home")} to="/">
|
||||||
{t("home", { capfirst: true })}
|
{isPhone ? <IconHome /> : t("home", { capfirst: true })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{user?.logged ? (
|
{user?.logged ? (
|
||||||
<NavLink className={"navLink"} aria-label={t("dashboard")} to="/dashboard/help">
|
<NavLink className={"navLink"} aria-label={t("dashboard")} to="/dashboard/help">
|
||||||
{t("dashboard", { capfirst: true })}
|
{isPhone ? <IconSettings /> : t("dashboard", { capfirst: true })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : null}
|
) : null}
|
||||||
</Group>
|
</Group>
|
||||||
@@ -34,7 +37,7 @@ export function Navbar() {
|
|||||||
className={"navLink"}
|
className={"navLink"}
|
||||||
aria-label={t("login with keycloak", { capfirst: true })}
|
aria-label={t("login with keycloak", { capfirst: true })}
|
||||||
>
|
>
|
||||||
{t("login with keycloak", { capfirst: true })}
|
{isPhone ? <IconLogin /> : t("login with keycloak", { capfirst: true })}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<a
|
<a
|
||||||
@@ -42,7 +45,7 @@ export function Navbar() {
|
|||||||
className={"navLink"}
|
className={"navLink"}
|
||||||
aria-label={t("logout", { capfirst: true })}
|
aria-label={t("logout", { capfirst: true })}
|
||||||
>
|
>
|
||||||
{t("logout", { capfirst: true })}
|
{isPhone ? <IconLogout /> : t("logout", { capfirst: true })}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
|
|||||||
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
|
}, [inputForm.values.cheque_quantity, price, inputForm.values.cheques]);
|
||||||
|
|
||||||
const paymentMethod = useMemo(() => {
|
const paymentMethod = useMemo(() => {
|
||||||
return productor?.payment_methods.find((el) => el.name === "cheque")
|
return productor?.payment_methods.find((el) => el.name === "cheque");
|
||||||
}, [productor]);
|
}, [productor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -57,7 +57,9 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
|
|||||||
{ capfirst: true },
|
{ capfirst: true },
|
||||||
)}
|
)}
|
||||||
min={1}
|
min={1}
|
||||||
max={paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3}
|
max={
|
||||||
|
paymentMethod?.max && paymentMethod?.max !== "" ? Number(paymentMethod?.max) : 3
|
||||||
|
}
|
||||||
{...inputForm.getInputProps(`cheque_quantity`)}
|
{...inputForm.getInputProps(`cheque_quantity`)}
|
||||||
/>
|
/>
|
||||||
<Group grow>
|
<Group grow>
|
||||||
@@ -67,11 +69,7 @@ export function ContractCheque({ inputForm, price, productor }: ContractChequePr
|
|||||||
label={t("cheque id", { capfirst: true })}
|
label={t("cheque id", { capfirst: true })}
|
||||||
placeholder={t("cheque id", { capfirst: true })}
|
placeholder={t("cheque id", { capfirst: true })}
|
||||||
{...inputForm.getInputProps(`cheques.${index}.name`)}
|
{...inputForm.getInputProps(`cheques.${index}.name`)}
|
||||||
error={
|
error={cheque.name == "" ? inputForm?.errors.cheques : null}
|
||||||
cheque.name == "" ?
|
|
||||||
inputForm?.errors.cheques :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
readOnly
|
readOnly
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
type ProductorInputs,
|
type ProductorInputs,
|
||||||
} from "@/services/resources/productors";
|
} from "@/services/resources/productors";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useGetRoles } from "@/services/api";
|
import { useAuth } from "@/services/auth/AuthProvider";
|
||||||
|
|
||||||
export type ProductorModalProps = ModalBaseProps & {
|
export type ProductorModalProps = ModalBaseProps & {
|
||||||
currentProductor?: Productor;
|
currentProductor?: Productor;
|
||||||
@@ -32,7 +32,7 @@ export function ProductorModal({
|
|||||||
currentProductor,
|
currentProductor,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
}: ProductorModalProps) {
|
}: ProductorModalProps) {
|
||||||
const { data: allRoles } = useGetRoles();
|
const { loggedUser } = useAuth();
|
||||||
|
|
||||||
const form = useForm<ProductorInputs>({
|
const form = useForm<ProductorInputs>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -49,20 +49,30 @@ export function ProductorModal({
|
|||||||
type: (value) =>
|
type: (value) =>
|
||||||
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("type", { capfirst: true })} ${t("is required")}` : null,
|
||||||
payment_methods: (value) =>
|
payment_methods: (value) =>
|
||||||
value.length === 0 || value.some(
|
value.length === 0 ||
|
||||||
(payment) =>
|
value.some((payment) => payment.name === "cheque" && payment.details === "")
|
||||||
payment.name === "cheque" &&
|
? `${t("a payment method", { capfirst: true })} ${t("is required")}`
|
||||||
payment.details === "") ?
|
: null,
|
||||||
`${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleSelect = useMemo(() => {
|
const roleSelect = useMemo(() => {
|
||||||
return allRoles?.map((role) => ({ value: String(role.name), label: role.name }));
|
return loggedUser?.user?.roles?.map((role) => ({
|
||||||
}, [allRoles]);
|
value: String(role.name),
|
||||||
|
label: role.name,
|
||||||
|
}));
|
||||||
|
}, [loggedUser?.user?.roles]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal opened={opened} onClose={onClose} title={t("create productor", { capfirst: true })}>
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
currentProductor
|
||||||
|
? t("edit productor", { capfirst: true })
|
||||||
|
: t("create productor", { capfirst: true })
|
||||||
|
}
|
||||||
|
>
|
||||||
<Title order={4}>{t("Informations", { capfirst: true })}</Title>
|
<Title order={4}>{t("Informations", { capfirst: true })}</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("productor name", { capfirst: true })}
|
label={t("productor name", { capfirst: true })}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ActionIcon, Badge, Table, Tooltip } from "@mantine/core";
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import type { Productor } from "@/services/resources/productors";
|
import type { Productor } from "@/services/resources/productors";
|
||||||
import { useDeleteProductor } from "@/services/api";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type ProductorRowProps = {
|
export type ProductorRowProps = {
|
||||||
@@ -11,7 +10,6 @@ export type ProductorRowProps = {
|
|||||||
|
|
||||||
export default function ProductorRow({ productor }: ProductorRowProps) {
|
export default function ProductorRow({ productor }: ProductorRowProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteProductor();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -46,8 +44,11 @@ export default function ProductorRow({ productor }: ProductorRowProps) {
|
|||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(productor.id);
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/dashboard/productors/${productor.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
|
|||||||
});
|
});
|
||||||
|
|
||||||
const productorsSelect = useMemo(() => {
|
const productorsSelect = useMemo(() => {
|
||||||
|
if (!productors) return [];
|
||||||
return productors?.map((productor) => ({
|
return productors?.map((productor) => ({
|
||||||
value: String(productor.id),
|
value: String(productor.id),
|
||||||
label: `${productor.name}`,
|
label: `${productor.name}`,
|
||||||
@@ -66,7 +67,15 @@ export function ProductModal({ opened, onClose, currentProduct, handleSubmit }:
|
|||||||
}, [productors]);
|
}, [productors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal opened={opened} onClose={onClose} title={t("create product", { capfirst: true })}>
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
currentProduct
|
||||||
|
? t("edit product", { capfirst: true })
|
||||||
|
: t("create product", { capfirst: true })
|
||||||
|
}
|
||||||
|
>
|
||||||
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
||||||
<Select
|
<Select
|
||||||
label={t("productor", { capfirst: true })}
|
label={t("productor", { capfirst: true })}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
|
import { ProductType, ProductUnit, type Product } from "@/services/resources/products";
|
||||||
import { useDeleteProduct } from "@/services/api";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type ProductRowProps = {
|
export type ProductRowProps = {
|
||||||
@@ -11,7 +10,6 @@ export type ProductRowProps = {
|
|||||||
|
|
||||||
export default function ProductRow({ product }: ProductRowProps) {
|
export default function ProductRow({ product }: ProductRowProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteProduct();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,8 +57,11 @@ export default function ProductRow({ product }: ProductRowProps) {
|
|||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(product.id);
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/dashboard/products/${product.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export default function ShipmentModal({
|
|||||||
const { data: allProductors } = useGetProductors();
|
const { data: allProductors } = useGetProductors();
|
||||||
|
|
||||||
const formsSelect = useMemo(() => {
|
const formsSelect = useMemo(() => {
|
||||||
|
if (!allForms) return [];
|
||||||
return allForms?.map((currentForm) => ({
|
return allForms?.map((currentForm) => ({
|
||||||
value: String(currentForm.id),
|
value: String(currentForm.id),
|
||||||
label: `${currentForm.name} ${currentForm.season}`,
|
label: `${currentForm.name} ${currentForm.season}`,
|
||||||
@@ -55,7 +56,7 @@ export default function ShipmentModal({
|
|||||||
}, [allForms]);
|
}, [allForms]);
|
||||||
|
|
||||||
const productsSelect = useMemo(() => {
|
const productsSelect = useMemo(() => {
|
||||||
if (!allProducts || !allProductors) return;
|
if (!allProducts || !allProductors) return [];
|
||||||
return allProductors?.map((productor) => {
|
return allProductors?.map((productor) => {
|
||||||
return {
|
return {
|
||||||
group: productor.name,
|
group: productor.name,
|
||||||
@@ -73,7 +74,11 @@ export default function ShipmentModal({
|
|||||||
<Modal
|
<Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={currentShipment ? t("edit shipment") : t("create shipment")}
|
title={
|
||||||
|
currentShipment
|
||||||
|
? t("edit shipment", { capfirst: true })
|
||||||
|
: t("create shipment", { capfirst: true })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("shipment name", { capfirst: true })}
|
label={t("shipment name", { capfirst: true })}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
import { ActionIcon, Table, Tooltip } from "@mantine/core";
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useDeleteShipment } from "@/services/api";
|
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import type { Shipment } from "@/services/resources/shipments";
|
import type { Shipment } from "@/services/resources/shipments";
|
||||||
@@ -11,7 +10,6 @@ export type ShipmentRowProps = {
|
|||||||
|
|
||||||
export default function ShipmentRow({ shipment }: ShipmentRowProps) {
|
export default function ShipmentRow({ shipment }: ShipmentRowProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteShipment();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +18,7 @@ export default function ShipmentRow({ shipment }: ShipmentRowProps) {
|
|||||||
<Table.Td>{shipment.date}</Table.Td>
|
<Table.Td>{shipment.date}</Table.Td>
|
||||||
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
|
<Table.Td>{`${shipment.form.name} ${shipment.form.season}`}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Tooltip label={t("edit productor", { capfirst: true })}>
|
<Tooltip label={t("edit shipment", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
@@ -34,13 +32,16 @@ export default function ShipmentRow({ shipment }: ShipmentRowProps) {
|
|||||||
<IconEdit />
|
<IconEdit />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={t("remove productor", { capfirst: true })}>
|
<Tooltip label={t("remove shipment", { capfirst: true })}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(shipment.id);
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/dashboard/shipments/${shipment.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
@@ -36,11 +36,20 @@ export function UserModal({ opened, onClose, currentUser, handleSubmit }: UserMo
|
|||||||
});
|
});
|
||||||
|
|
||||||
const roleSelect = useMemo(() => {
|
const roleSelect = useMemo(() => {
|
||||||
|
if (!allRoles) return [];
|
||||||
return allRoles?.map((role) => ({ value: String(role.name), label: role.name }));
|
return allRoles?.map((role) => ({ value: String(role.name), label: role.name }));
|
||||||
}, [allRoles]);
|
}, [allRoles]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal opened={opened} onClose={onClose} title={t("create user", { capfirst: true })}>
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
currentUser
|
||||||
|
? t("edit user", { capfirst: true })
|
||||||
|
: t("create user", { capfirst: true })
|
||||||
|
}
|
||||||
|
>
|
||||||
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
<Title order={4}>{t("informations", { capfirst: true })}</Title>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("user name", { capfirst: true })}
|
label={t("user name", { capfirst: true })}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ActionIcon, Badge, Box, Table, Tooltip } from "@mantine/core";
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { IconEdit, IconX } from "@tabler/icons-react";
|
import { IconEdit, IconX } from "@tabler/icons-react";
|
||||||
import { type User } from "@/services/resources/users";
|
import { type User } from "@/services/resources/users";
|
||||||
import { useDeleteUser } from "@/services/api";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
|
|
||||||
export type UserRowProps = {
|
export type UserRowProps = {
|
||||||
@@ -11,7 +10,6 @@ export type UserRowProps = {
|
|||||||
|
|
||||||
export default function UserRow({ user }: UserRowProps) {
|
export default function UserRow({ user }: UserRowProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const deleteMutation = useDeleteUser();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -21,8 +19,8 @@ export default function UserRow({ user }: UserRowProps) {
|
|||||||
<Table.Td style={{ maxWidth: 200 }}>
|
<Table.Td style={{ maxWidth: 200 }}>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
gap: 4
|
gap: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user.roles.slice(0, 3).map((value) => (
|
{user.roles.slice(0, 3).map((value) => (
|
||||||
@@ -30,17 +28,13 @@ export default function UserRow({ user }: UserRowProps) {
|
|||||||
{t(value.name, { capfirst: true })}
|
{t(value.name, { capfirst: true })}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
{
|
{user.roles.length > 3 && (
|
||||||
user.roles.length > 3 && (
|
<Tooltip label={user.roles.slice(3).map((role) => `${role.name} `)}>
|
||||||
<Tooltip
|
|
||||||
label={user.roles.slice(3).map(role=>`${role.name} `)}
|
|
||||||
>
|
|
||||||
<Badge size="xs" variant="light">
|
<Badge size="xs" variant="light">
|
||||||
+{user.roles.length - 3}
|
+{user.roles.length - 3}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)}
|
||||||
}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
@@ -63,8 +57,11 @@ export default function UserRow({ user }: UserRowProps) {
|
|||||||
color="red"
|
color="red"
|
||||||
size="sm"
|
size="sm"
|
||||||
mr="5"
|
mr="5"
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
deleteMutation.mutate(user.id);
|
e.stopPropagation();
|
||||||
|
navigate(
|
||||||
|
`/dashboard/users/${user.id}/delete${searchParams ? `?${searchParams.toString()}` : ""}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ export function Contract() {
|
|||||||
payment_method: (value) =>
|
payment_method: (value) =>
|
||||||
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
!value ? `${t("a payment method", { capfirst: true })} ${t("is required")}` : null,
|
||||||
cheques: (value, values) =>
|
cheques: (value, values) =>
|
||||||
values.payment_method === "cheque" && value.some((val) => val.name == "") ? `${t("cheque id", {capfirst: true})} ${t("is required")}` : null,
|
values.payment_method === "cheque" && value.some((val) => val.name == "")
|
||||||
|
? `${t("cheque id", { capfirst: true })} ${t("is required")}`
|
||||||
|
: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,12 +139,13 @@ export function Contract() {
|
|||||||
const formValues = inputForm.getValues();
|
const formValues = inputForm.getValues();
|
||||||
const contract = {
|
const contract = {
|
||||||
...formValues,
|
...formValues,
|
||||||
cheque_quantity: formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
|
cheque_quantity:
|
||||||
|
formValues.payment_method === "cheque" ? formValues.cheque_quantity : 0,
|
||||||
form_id: form.id,
|
form_id: form.id,
|
||||||
products: tranformProducts(withDefaultValues(formValues.products)),
|
products: tranformProducts(withDefaultValues(formValues.products)),
|
||||||
};
|
};
|
||||||
await createContractMutation.mutateAsync(contract);
|
await createContractMutation.mutateAsync(contract);
|
||||||
window.location.href = '/';
|
window.location.href = "/";
|
||||||
} else {
|
} else {
|
||||||
const firstErrorField = Object.keys(errors.errors)[0];
|
const firstErrorField = Object.keys(errors.errors)[0];
|
||||||
const ref = inputRefs.current[firstErrorField];
|
const ref = inputRefs.current[firstErrorField];
|
||||||
@@ -165,7 +168,7 @@ export function Contract() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack w={{ base: "100%", md: "80%", lg: "50%" }}>
|
<Stack w={{ base: "100%", md: "80%", lg: "50%" }} p={{ base: "xs" }}>
|
||||||
<Title order={2}>{form.name}</Title>
|
<Title order={2}>{form.name}</Title>
|
||||||
<Title order={3}>{t("informations", { capfirst: true })}</Title>
|
<Title order={3}>{t("informations", { capfirst: true })}</Title>
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
@@ -283,13 +286,13 @@ export function Contract() {
|
|||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
inputRefs.current.payment_method = el;
|
inputRefs.current.payment_method = el;
|
||||||
}}
|
}}
|
||||||
|
comboboxProps={{
|
||||||
|
withinPortal: false,
|
||||||
|
position: "bottom-start",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{inputForm.values.payment_method === "cheque" ? (
|
{inputForm.values.payment_method === "cheque" ? (
|
||||||
<ContractCheque
|
<ContractCheque productor={form?.productor} price={price} inputForm={inputForm} />
|
||||||
productor={form?.productor}
|
|
||||||
price={price}
|
|
||||||
inputForm={inputForm}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
{inputForm.values.payment_method === "transfer" ? (
|
{inputForm.values.payment_method === "transfer" ? (
|
||||||
<Text>
|
<Text>
|
||||||
@@ -318,8 +321,10 @@ export function Contract() {
|
|||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconDownload />}
|
leftSection={<IconDownload />}
|
||||||
aria-label={t("submit contracts")} onClick={handleSubmit}>
|
aria-label={t("submit contracts")}
|
||||||
{t("submit contract", {capfirst: true})}
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
{t("submit", { capfirst: true })}
|
||||||
</Button>
|
</Button>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useGetAllContractFile, useGetContracts, useGetRecap } from "@/services/api";
|
import {
|
||||||
|
useDeleteContract,
|
||||||
|
useGetAllContractFile,
|
||||||
|
useGetContract,
|
||||||
|
useGetContracts,
|
||||||
|
useGetRecap,
|
||||||
|
} from "@/services/api";
|
||||||
import { IconDownload, IconTableExport } from "@tabler/icons-react";
|
import { IconDownload, IconTableExport } from "@tabler/icons-react";
|
||||||
import ContractRow from "@/components/Contracts/Row";
|
import ContractRow from "@/components/Contracts/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
@@ -8,6 +14,7 @@ import { ContractModal } from "@/components/Contracts/Modal";
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { type Contract } from "@/services/resources/contracts";
|
import { type Contract } from "@/services/resources/contracts";
|
||||||
import ContractsFilters from "@/components/Contracts/Filter";
|
import ContractsFilters from "@/components/Contracts/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export default function Contracts() {
|
export default function Contracts() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -17,16 +24,29 @@ export default function Contracts() {
|
|||||||
const getRecapMutation = useGetRecap();
|
const getRecapMutation = useGetRecap();
|
||||||
const isdownload = location.pathname.includes("/download");
|
const isdownload = location.pathname.includes("/download");
|
||||||
const isrecap = location.pathname.includes("/export");
|
const isrecap = location.pathname.includes("/export");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
|
const deleteId = useMemo(() => {
|
||||||
|
if (isDelete) {
|
||||||
|
return location.pathname.split("/")[3];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
navigate(`/dashboard/contracts${searchParams ? `?${searchParams.toString()}` : ""}`);
|
navigate(`/dashboard/contracts${searchParams ? `?${searchParams.toString()}` : ""}`);
|
||||||
}, [navigate, searchParams]);
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const { data: contracts, isPending } = useGetContracts(searchParams);
|
const { data: contracts, isPending } = useGetContracts(searchParams);
|
||||||
|
const { data: currentContract } = useGetContract(Number(deleteId), {
|
||||||
|
enabled: !!deleteId,
|
||||||
|
});
|
||||||
|
|
||||||
const { data: allContracts } = useGetContracts();
|
const { data: allContracts } = useGetContracts();
|
||||||
|
const deleteContractMutation = useDeleteContract();
|
||||||
|
|
||||||
const forms = useMemo(() => {
|
const forms = useMemo(() => {
|
||||||
|
if (!allContracts) return [];
|
||||||
return allContracts
|
return allContracts
|
||||||
?.map((contract: Contract) => contract.form.name)
|
?.map((contract: Contract) => contract.form.name)
|
||||||
.filter((contract, index, array) => array.indexOf(contract) === index);
|
.filter((contract, index, array) => array.indexOf(contract) === index);
|
||||||
@@ -59,7 +79,7 @@ export default function Contracts() {
|
|||||||
await getRecapMutation.mutateAsync(id);
|
await getRecapMutation.mutateAsync(id);
|
||||||
},
|
},
|
||||||
[getAllContractFilesMutation],
|
[getAllContractFilesMutation],
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!contracts || isPending)
|
if (!contracts || isPending)
|
||||||
return (
|
return (
|
||||||
@@ -85,11 +105,9 @@ export default function Contracts() {
|
|||||||
<IconDownload />
|
<IconDownload />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip label={t("download recap", { capfirst: true })}>
|
||||||
label={t("download recap", { capfirst: true })}
|
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
disabled={true}
|
disabled={false}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(
|
navigate(
|
||||||
@@ -112,6 +130,18 @@ export default function Contracts() {
|
|||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
handleSubmit={handleDownloadRecap}
|
handleSubmit={handleDownloadRecap}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteContractMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"contract"}
|
||||||
|
entity={{
|
||||||
|
name: `${currentContract?.form.name} ${currentContract?.firstname} ${currentContract?.lastname}`,
|
||||||
|
id: currentContract?.id || 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<ContractsFilters
|
<ContractsFilters
|
||||||
forms={forms || []}
|
forms={forms || []}
|
||||||
|
|||||||
@@ -16,17 +16,50 @@ export default function Dashboard() {
|
|||||||
onChange={(value) => navigate(`/dashboard/${value}`)}
|
onChange={(value) => navigate(`/dashboard/${value}`)}
|
||||||
>
|
>
|
||||||
<Tabs.List mb="md">
|
<Tabs.List mb="md">
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/help" {...props}></Link>)} value="help">{t("help", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/productors" {...props}></Link>)} value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
|
renderRoot={(props) => <Link to="/dashboard/help" {...props}></Link>}
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/products" {...props}></Link>)} value="products">{t("products", { capfirst: true })}</Tabs.Tab>
|
value="help"
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/forms" {...props}></Link>)} value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>
|
>
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/shipments" {...props}></Link>)} value="shipments">{t("shipments", { capfirst: true })}</Tabs.Tab>
|
{t("help", { capfirst: true })}
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/contracts" {...props}></Link>)} value="contracts">{t("contracts", { capfirst: true })}</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
{
|
<Tabs.Tab
|
||||||
loggedUser?.user?.roles && loggedUser?.user?.roles?.length > 5 ?
|
renderRoot={(props) => <Link to="/dashboard/productors" {...props}></Link>}
|
||||||
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/users" {...props}></Link>)} value="users">{t("users", { capfirst: true })}</Tabs.Tab> :
|
value="productors"
|
||||||
null
|
>
|
||||||
}
|
{t("productors", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
renderRoot={(props) => <Link to="/dashboard/products" {...props}></Link>}
|
||||||
|
value="products"
|
||||||
|
>
|
||||||
|
{t("products", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
renderRoot={(props) => <Link to="/dashboard/forms" {...props}></Link>}
|
||||||
|
value="forms"
|
||||||
|
>
|
||||||
|
{t("forms", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
renderRoot={(props) => <Link to="/dashboard/shipments" {...props}></Link>}
|
||||||
|
value="shipments"
|
||||||
|
>
|
||||||
|
{t("shipments", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab
|
||||||
|
renderRoot={(props) => <Link to="/dashboard/contracts" {...props}></Link>}
|
||||||
|
value="contracts"
|
||||||
|
>
|
||||||
|
{t("contracts", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
{loggedUser?.user?.roles && loggedUser?.user?.roles?.length > 5 ? (
|
||||||
|
<Tabs.Tab
|
||||||
|
renderRoot={(props) => <Link to="/dashboard/users" {...props}></Link>}
|
||||||
|
value="users"
|
||||||
|
>
|
||||||
|
{t("users", { capfirst: true })}
|
||||||
|
</Tabs.Tab>
|
||||||
|
) : null}
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
import { Stack, Loader, Title, Group, ActionIcon, Tooltip, Table, ScrollArea } from "@mantine/core";
|
||||||
import { useCreateForm, useEditForm, useGetForm, useGetReferentForms } from "@/services/api";
|
import {
|
||||||
|
useCreateForm,
|
||||||
|
useDeleteForm,
|
||||||
|
useEditForm,
|
||||||
|
useGetForm,
|
||||||
|
useGetReferentForms,
|
||||||
|
} from "@/services/api";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
@@ -8,6 +14,7 @@ import FormModal from "@/components/Forms/Modal";
|
|||||||
import FormRow from "@/components/Forms/Row";
|
import FormRow from "@/components/Forms/Row";
|
||||||
import type { Form, FormInputs } from "@/services/resources/forms";
|
import type { Form, FormInputs } from "@/services/resources/forms";
|
||||||
import FilterForms from "@/components/Forms/Filter";
|
import FilterForms from "@/components/Forms/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export function Forms() {
|
export function Forms() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -16,9 +23,10 @@ export function Forms() {
|
|||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/forms/create";
|
const isCreate = location.pathname === "/dashboard/forms/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit || isDelete) {
|
||||||
return location.pathname.split("/")[3];
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -36,12 +44,14 @@ export function Forms() {
|
|||||||
const { data: allForms } = useGetReferentForms();
|
const { data: allForms } = useGetReferentForms();
|
||||||
|
|
||||||
const seasons = useMemo(() => {
|
const seasons = useMemo(() => {
|
||||||
|
if (!allForms) return [];
|
||||||
return allForms
|
return allForms
|
||||||
?.map((form: Form) => form.season)
|
?.map((form: Form) => form.season)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
}, [allForms]);
|
}, [allForms]);
|
||||||
|
|
||||||
const productors = useMemo(() => {
|
const productors = useMemo(() => {
|
||||||
|
if (!allForms) return [];
|
||||||
return allForms
|
return allForms
|
||||||
?.map((form: Form) => form.productor.name)
|
?.map((form: Form) => form.productor.name)
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index);
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
@@ -49,6 +59,7 @@ export function Forms() {
|
|||||||
|
|
||||||
const createFormMutation = useCreateForm();
|
const createFormMutation = useCreateForm();
|
||||||
const editFormMutation = useEditForm();
|
const editFormMutation = useEditForm();
|
||||||
|
const deleteFormMutation = useDeleteForm();
|
||||||
|
|
||||||
const handleCreateForm = useCallback(
|
const handleCreateForm = useCallback(
|
||||||
async (form: FormInputs) => {
|
async (form: FormInputs) => {
|
||||||
@@ -144,6 +155,15 @@ export function Forms() {
|
|||||||
currentForm={currentForm}
|
currentForm={currentForm}
|
||||||
handleSubmit={handleEditForm}
|
handleSubmit={handleEditForm}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteFormMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"form"}
|
||||||
|
entity={currentForm}
|
||||||
|
/>
|
||||||
<ScrollArea type="auto">
|
<ScrollArea type="auto">
|
||||||
<Table striped>
|
<Table striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
|
|||||||
@@ -245,7 +245,10 @@ export function Help() {
|
|||||||
<Title order={3}>{t("export contracts", { capfirst: true })}</Title>
|
<Title order={3}>{t("export contracts", { capfirst: true })}</Title>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>
|
<Text>
|
||||||
{t("to export contracts submissions before sending to the productor go to the contracts section", {capfirst: true})}
|
{t(
|
||||||
|
"to export contracts submissions before sending to the productor go to the contracts section",
|
||||||
|
{ capfirst: true },
|
||||||
|
)}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
ml="4"
|
ml="4"
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -260,21 +263,32 @@ export function Help() {
|
|||||||
<IconLink />
|
<IconLink />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{t("in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract", {capfirst: true})}</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
{t("you can download all contracts for your form using the export all", {capfirst: true})}{" "}
|
{t(
|
||||||
|
"in this page you can view all contracts submissions, you can remove duplicates submission or download a specific contract",
|
||||||
|
{ capfirst: true },
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{t("you can download all contracts for your form using the export all", {
|
||||||
|
capfirst: true,
|
||||||
|
})}{" "}
|
||||||
<ActionIcon size="sm">
|
<ActionIcon size="sm">
|
||||||
<IconDownload />
|
<IconDownload />
|
||||||
</ActionIcon>{" "}
|
</ActionIcon>{" "}
|
||||||
{t("button in top right of the page", { section: t("contracts") })}{" "}
|
{t("button in top right of the page", { section: t("contracts") })}{" "}
|
||||||
{t("in the same corner you can download a recap by clicking on the button", {capfirst: true})}{" "}
|
{t("in the same corner you can download a recap by clicking on the button", {
|
||||||
|
capfirst: true,
|
||||||
|
})}{" "}
|
||||||
<ActionIcon size="sm">
|
<ActionIcon size="sm">
|
||||||
<IconTableExport />
|
<IconTableExport />
|
||||||
</ActionIcon>{" "}
|
</ActionIcon>{" "}
|
||||||
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text>
|
||||||
{t("once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page", {capfirst: true})}
|
{t(
|
||||||
|
"once all contracts downloaded, you can delete the form (to avoid new submissions) and hide it from the home page",
|
||||||
|
{ capfirst: true },
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Title order={3}>{t("glossary", { capfirst: true })}</Title>
|
<Title order={3}>{t("glossary", { capfirst: true })}</Title>
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ export function Home() {
|
|||||||
if (searchParams.get("userNotAllowed")) {
|
if (searchParams.get("userNotAllowed")) {
|
||||||
showNotification({
|
showNotification({
|
||||||
title: t("user not allowed", { capfirst: true }),
|
title: t("user not allowed", { capfirst: true }),
|
||||||
message: t("your keycloak user has no roles, please contact your administrator", {capfirst: true}),
|
message: t("your keycloak user has no roles, please contact your administrator", {
|
||||||
|
capfirst: true,
|
||||||
|
}),
|
||||||
color: "red",
|
color: "red",
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [searchParams])
|
}, [searchParams]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack mt="lg">
|
<Stack mt="lg">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } f
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import {
|
import {
|
||||||
useCreateProductor,
|
useCreateProductor,
|
||||||
|
useDeleteProductor,
|
||||||
useEditProductor,
|
useEditProductor,
|
||||||
useGetProductor,
|
useGetProductor,
|
||||||
useGetProductors,
|
useGetProductors,
|
||||||
@@ -13,6 +14,7 @@ import { ProductorModal } from "@/components/Productors/Modal";
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
import type { Productor, ProductorInputs } from "@/services/resources/productors";
|
||||||
import ProductorsFilters from "@/components/Productors/Filter";
|
import ProductorsFilters from "@/components/Productors/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export default function Productors() {
|
export default function Productors() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -23,9 +25,10 @@ export default function Productors() {
|
|||||||
const { data: allProductors } = useGetProductors();
|
const { data: allProductors } = useGetProductors();
|
||||||
const isCreate = location.pathname === "/dashboard/productors/create";
|
const isCreate = location.pathname === "/dashboard/productors/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit || isDelete) {
|
||||||
return location.pathname.split("/")[3];
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -40,12 +43,14 @@ export default function Productors() {
|
|||||||
}, [navigate, searchParams]);
|
}, [navigate, searchParams]);
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const names = useMemo(() => {
|
||||||
|
if (!allProductors) return [];
|
||||||
return allProductors
|
return allProductors
|
||||||
?.map((productor: Productor) => productor.name)
|
?.map((productor: Productor) => productor.name)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
}, [allProductors]);
|
}, [allProductors]);
|
||||||
|
|
||||||
const types = useMemo(() => {
|
const types = useMemo(() => {
|
||||||
|
if (!allProductors) return [];
|
||||||
return allProductors
|
return allProductors
|
||||||
?.map((productor: Productor) => productor.type)
|
?.map((productor: Productor) => productor.type)
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index);
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
@@ -53,6 +58,7 @@ export default function Productors() {
|
|||||||
|
|
||||||
const createProductorMutation = useCreateProductor();
|
const createProductorMutation = useCreateProductor();
|
||||||
const editProductorMutation = useEditProductor();
|
const editProductorMutation = useEditProductor();
|
||||||
|
const deleteProductorMutation = useDeleteProductor();
|
||||||
|
|
||||||
const handleCreateProductor = useCallback(
|
const handleCreateProductor = useCallback(
|
||||||
async (productor: ProductorInputs) => {
|
async (productor: ProductorInputs) => {
|
||||||
@@ -61,8 +67,8 @@ export default function Productors() {
|
|||||||
payment_methods: productor.payment_methods.map((payment) => ({
|
payment_methods: productor.payment_methods.map((payment) => ({
|
||||||
name: payment.name,
|
name: payment.name,
|
||||||
details: payment.details,
|
details: payment.details,
|
||||||
max: payment.max === "" ? null : payment.max
|
max: payment.max === "" ? null : payment.max,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
@@ -79,8 +85,8 @@ export default function Productors() {
|
|||||||
payment_methods: productor.payment_methods.map((payment) => ({
|
payment_methods: productor.payment_methods.map((payment) => ({
|
||||||
name: payment.name,
|
name: payment.name,
|
||||||
details: payment.details,
|
details: payment.details,
|
||||||
max: payment.max === "" ? null : payment.max
|
max: payment.max === "" ? null : payment.max,
|
||||||
}))
|
})),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
closeModal();
|
closeModal();
|
||||||
@@ -139,6 +145,15 @@ export default function Productors() {
|
|||||||
currentProductor={currentProductor}
|
currentProductor={currentProductor}
|
||||||
handleSubmit={handleEditProductor}
|
handleSubmit={handleEditProductor}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteProductorMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"productor"}
|
||||||
|
entity={currentProductor}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<ProductorsFilters
|
<ProductorsFilters
|
||||||
names={names || []}
|
names={names || []}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateProduct, useEditProduct, useGetProduct, useGetProducts } from "@/services/api";
|
import {
|
||||||
|
useCreateProduct,
|
||||||
|
useDeleteProduct,
|
||||||
|
useEditProduct,
|
||||||
|
useGetProduct,
|
||||||
|
useGetProducts,
|
||||||
|
} from "@/services/api";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import ProductRow from "@/components/Products/Row";
|
import ProductRow from "@/components/Products/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
@@ -12,6 +18,7 @@ import {
|
|||||||
type ProductInputs,
|
type ProductInputs,
|
||||||
} from "@/services/resources/products";
|
} from "@/services/resources/products";
|
||||||
import ProductsFilters from "@/components/Products/Filter";
|
import ProductsFilters from "@/components/Products/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export default function Products() {
|
export default function Products() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -19,9 +26,10 @@ export default function Products() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isCreate = location.pathname === "/dashboard/products/create";
|
const isCreate = location.pathname === "/dashboard/products/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit || isDelete) {
|
||||||
return location.pathname.split("/")[3];
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -38,12 +46,14 @@ export default function Products() {
|
|||||||
const { data: allProducts } = useGetProducts();
|
const { data: allProducts } = useGetProducts();
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const names = useMemo(() => {
|
||||||
|
if (!allProducts) return [];
|
||||||
return allProducts
|
return allProducts
|
||||||
?.map((product: Product) => product.name)
|
?.map((product: Product) => product.name)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
}, [allProducts]);
|
}, [allProducts]);
|
||||||
|
|
||||||
const productors = useMemo(() => {
|
const productors = useMemo(() => {
|
||||||
|
if (!allProducts) return [];
|
||||||
return allProducts
|
return allProducts
|
||||||
?.map((product: Product) => product.productor.name)
|
?.map((product: Product) => product.productor.name)
|
||||||
.filter((productor, index, array) => array.indexOf(productor) === index);
|
.filter((productor, index, array) => array.indexOf(productor) === index);
|
||||||
@@ -51,6 +61,7 @@ export default function Products() {
|
|||||||
|
|
||||||
const createProductMutation = useCreateProduct();
|
const createProductMutation = useCreateProduct();
|
||||||
const editProductMutation = useEditProduct();
|
const editProductMutation = useEditProduct();
|
||||||
|
const deleteProductMutation = useDeleteProduct();
|
||||||
|
|
||||||
const handleCreateProduct = useCallback(
|
const handleCreateProduct = useCallback(
|
||||||
async (product: ProductInputs) => {
|
async (product: ProductInputs) => {
|
||||||
@@ -130,6 +141,15 @@ export default function Products() {
|
|||||||
currentProduct={currentProduct}
|
currentProduct={currentProduct}
|
||||||
handleSubmit={handleEditProduct}
|
handleSubmit={handleEditProduct}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteProductMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"product"}
|
||||||
|
entity={currentProduct}
|
||||||
|
/>
|
||||||
<ScrollArea type="auto">
|
<ScrollArea type="auto">
|
||||||
<Table striped>
|
<Table striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } f
|
|||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import {
|
import {
|
||||||
useCreateShipment,
|
useCreateShipment,
|
||||||
|
useDeleteShipment,
|
||||||
useEditShipment,
|
useEditShipment,
|
||||||
useGetShipment,
|
useGetShipment,
|
||||||
useGetShipments,
|
useGetShipments,
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
} from "@/services/resources/shipments";
|
} from "@/services/resources/shipments";
|
||||||
import ShipmentModal from "@/components/Shipments/Modal";
|
import ShipmentModal from "@/components/Shipments/Modal";
|
||||||
import ShipmentsFilters from "@/components/Shipments/Filter";
|
import ShipmentsFilters from "@/components/Shipments/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export default function Shipments() {
|
export default function Shipments() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -25,9 +27,10 @@ export default function Shipments() {
|
|||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/shipments/create";
|
const isCreate = location.pathname === "/dashboard/shipments/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit || isDelete) {
|
||||||
return location.pathname.split("/")[3];
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -44,12 +47,14 @@ export default function Shipments() {
|
|||||||
const { data: allShipments } = useGetShipments();
|
const { data: allShipments } = useGetShipments();
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const names = useMemo(() => {
|
||||||
|
if (!allShipments) return [];
|
||||||
return allShipments
|
return allShipments
|
||||||
?.map((shipment: Shipment) => shipment.name)
|
?.map((shipment: Shipment) => shipment.name)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
}, [allShipments]);
|
}, [allShipments]);
|
||||||
|
|
||||||
const forms = useMemo(() => {
|
const forms = useMemo(() => {
|
||||||
|
if (!allShipments) return [];
|
||||||
return allShipments
|
return allShipments
|
||||||
?.map((shipment: Shipment) => shipment.form.name)
|
?.map((shipment: Shipment) => shipment.form.name)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
@@ -57,6 +62,7 @@ export default function Shipments() {
|
|||||||
|
|
||||||
const createShipmentMutation = useCreateShipment();
|
const createShipmentMutation = useCreateShipment();
|
||||||
const editShipmentMutation = useEditShipment();
|
const editShipmentMutation = useEditShipment();
|
||||||
|
const deleteShipmentMutation = useDeleteShipment();
|
||||||
|
|
||||||
const handleCreateShipment = useCallback(
|
const handleCreateShipment = useCallback(
|
||||||
async (shipment: ShipmentInputs) => {
|
async (shipment: ShipmentInputs) => {
|
||||||
@@ -129,6 +135,15 @@ export default function Shipments() {
|
|||||||
currentShipment={currentShipment}
|
currentShipment={currentShipment}
|
||||||
handleSubmit={handleEditShipment}
|
handleSubmit={handleEditShipment}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteShipmentMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"shipment"}
|
||||||
|
entity={currentShipment}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<ShipmentsFilters
|
<ShipmentsFilters
|
||||||
forms={forms || []}
|
forms={forms || []}
|
||||||
@@ -147,7 +162,7 @@ export default function Shipments() {
|
|||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{shipments.map((shipment) => (
|
{shipments?.map((shipment) => (
|
||||||
<ShipmentRow shipment={shipment} key={shipment.id} />
|
<ShipmentRow shipment={shipment} key={shipment.id} />
|
||||||
))}
|
))}
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
import { ActionIcon, Group, Loader, ScrollArea, Stack, Table, Title, Tooltip } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { useCreateUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
|
import { useCreateUser, useDeleteUser, useEditUser, useGetUser, useGetUsers } from "@/services/api";
|
||||||
import { IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import UserRow from "@/components/Users/Row";
|
import UserRow from "@/components/Users/Row";
|
||||||
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
||||||
@@ -8,6 +8,7 @@ import { UserModal } from "@/components/Users/Modal";
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { type User, type UserInputs } from "@/services/resources/users";
|
import { type User, type UserInputs } from "@/services/resources/users";
|
||||||
import UsersFilters from "@/components/Users/Filter";
|
import UsersFilters from "@/components/Users/Filter";
|
||||||
|
import { DeleteModal } from "@/components/DeleteModal";
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -16,9 +17,10 @@ export default function Users() {
|
|||||||
|
|
||||||
const isCreate = location.pathname === "/dashboard/users/create";
|
const isCreate = location.pathname === "/dashboard/users/create";
|
||||||
const isEdit = location.pathname.includes("/edit");
|
const isEdit = location.pathname.includes("/edit");
|
||||||
|
const isDelete = location.pathname.includes("/delete");
|
||||||
|
|
||||||
const editId = useMemo(() => {
|
const editId = useMemo(() => {
|
||||||
if (isEdit) {
|
if (isEdit || isDelete) {
|
||||||
return location.pathname.split("/")[3];
|
return location.pathname.split("/")[3];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -36,6 +38,7 @@ export default function Users() {
|
|||||||
const { data: allUsers } = useGetUsers();
|
const { data: allUsers } = useGetUsers();
|
||||||
|
|
||||||
const names = useMemo(() => {
|
const names = useMemo(() => {
|
||||||
|
if (!allUsers) return [];
|
||||||
return allUsers
|
return allUsers
|
||||||
?.map((user: User) => user.name)
|
?.map((user: User) => user.name)
|
||||||
.filter((season, index, array) => array.indexOf(season) === index);
|
.filter((season, index, array) => array.indexOf(season) === index);
|
||||||
@@ -43,6 +46,7 @@ export default function Users() {
|
|||||||
|
|
||||||
const createUserMutation = useCreateUser();
|
const createUserMutation = useCreateUser();
|
||||||
const editUserMutation = useEditUser();
|
const editUserMutation = useEditUser();
|
||||||
|
const deleteUserMutation = useDeleteUser();
|
||||||
|
|
||||||
const handleCreateUser = useCallback(
|
const handleCreateUser = useCallback(
|
||||||
async (user: UserInputs) => {
|
async (user: UserInputs) => {
|
||||||
@@ -115,6 +119,15 @@ export default function Users() {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
handleSubmit={handleEditUser}
|
handleSubmit={handleEditUser}
|
||||||
/>
|
/>
|
||||||
|
<DeleteModal
|
||||||
|
opened={isDelete}
|
||||||
|
onClose={closeModal}
|
||||||
|
handleSubmit={(id: number) => {
|
||||||
|
deleteUserMutation.mutate(id);
|
||||||
|
}}
|
||||||
|
entityType={"user"}
|
||||||
|
entity={currentUser}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<UsersFilters
|
<UsersFilters
|
||||||
names={names || []}
|
names={names || []}
|
||||||
|
|||||||
@@ -34,20 +34,26 @@ export const router = createBrowserRouter([
|
|||||||
{ path: "productors", Component: Productors },
|
{ path: "productors", Component: Productors },
|
||||||
{ path: "productors/create", Component: Productors },
|
{ path: "productors/create", Component: Productors },
|
||||||
{ path: "productors/:id/edit", Component: Productors },
|
{ path: "productors/:id/edit", Component: Productors },
|
||||||
|
{ path: "productors/:id/delete", Component: Productors },
|
||||||
{ path: "products", Component: Products },
|
{ path: "products", Component: Products },
|
||||||
{ path: "products/create", Component: Products },
|
{ path: "products/create", Component: Products },
|
||||||
{ path: "products/:id/edit", Component: Products },
|
{ path: "products/:id/edit", Component: Products },
|
||||||
|
{ path: "products/:id/delete", Component: Products },
|
||||||
{ path: "contracts", Component: Contracts },
|
{ path: "contracts", Component: Contracts },
|
||||||
{ path: "contracts/download", Component: Contracts },
|
{ path: "contracts/download", Component: Contracts },
|
||||||
{ path: "contracts/export", Component: Contracts },
|
{ path: "contracts/export", Component: Contracts },
|
||||||
|
{ path: "contracts/:id/delete", Component: Contracts },
|
||||||
{ path: "users", Component: Users },
|
{ path: "users", Component: Users },
|
||||||
{ path: "users/create", Component: Users },
|
{ path: "users/create", Component: Users },
|
||||||
{ path: "users/:id/edit", Component: Users },
|
{ path: "users/:id/edit", Component: Users },
|
||||||
|
{ path: "users/:id/delete", Component: Users },
|
||||||
{ path: "forms", Component: Forms },
|
{ path: "forms", Component: Forms },
|
||||||
{ path: "forms/:id/edit", Component: Forms },
|
{ path: "forms/:id/edit", Component: Forms },
|
||||||
|
{ path: "forms/:id/delete", Component: Forms },
|
||||||
{ path: "forms/create", Component: Forms },
|
{ path: "forms/create", Component: Forms },
|
||||||
{ path: "shipments", Component: Shipments },
|
{ path: "shipments", Component: Shipments },
|
||||||
{ path: "shipments/:id/edit", Component: Shipments },
|
{ path: "shipments/:id/edit", Component: Shipments },
|
||||||
|
{ path: "shipments/:id/delete", Component: Shipments },
|
||||||
{ path: "shipments/create", Component: Shipments },
|
{ path: "shipments/create", Component: Shipments },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type { Product, ProductCreate, ProductEditPayload } from "./resources/pro
|
|||||||
import type { Contract, ContractCreate } from "./resources/contracts";
|
import type { Contract, ContractCreate } from "./resources/contracts";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
import type { DeleteDependencies, EntityName } from "./resources/common";
|
||||||
|
|
||||||
export async function refreshToken() {
|
export async function refreshToken() {
|
||||||
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"});
|
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"});
|
||||||
@@ -37,7 +38,7 @@ export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, r
|
|||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
const refresh = await refreshToken();
|
const refresh = await refreshToken();
|
||||||
if (refresh.status == 400 || refresh.status == 401) {
|
if (refresh.status !== 200) {
|
||||||
if (redirect)
|
if (redirect)
|
||||||
window.location.href = `/?sessionExpired=True`;
|
window.location.href = `/?sessionExpired=True`;
|
||||||
|
|
||||||
@@ -49,6 +50,13 @@ export async function fetchWithAuth(input: RequestInfo, options?: RequestInit, r
|
|||||||
credentials: "include",
|
credentials: "include",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
if (newRes.status === 401 || newRes.status === 403) {
|
||||||
|
if (redirect)
|
||||||
|
window.location.href = `/?sessionExpired=True`;
|
||||||
|
const error = new Error("Unauthorized");
|
||||||
|
error.cause = 401
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
return newRes;
|
return newRes;
|
||||||
}
|
}
|
||||||
if (res.status == 403) {
|
if (res.status == 403) {
|
||||||
@@ -321,6 +329,24 @@ export function useGetForm(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useGetDeleteDependencies(
|
||||||
|
entity: EntityName,
|
||||||
|
id?: number,
|
||||||
|
) {
|
||||||
|
return useQuery<DeleteDependencies[]>({
|
||||||
|
queryKey: [`${entity}_delete_preview_${id}`],
|
||||||
|
queryFn: () =>
|
||||||
|
fetchWithAuth(`${Config.backend_uri}/${entity}s/${id}/preview-delete`, {
|
||||||
|
credentials: "include",
|
||||||
|
}).then((res) => {
|
||||||
|
const result = res.json()
|
||||||
|
return result
|
||||||
|
}),
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
|
export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], Error> {
|
||||||
const queryString = filters?.toString();
|
const queryString = filters?.toString();
|
||||||
return useQuery<Form[]>({
|
return useQuery<Form[]>({
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import type { UserLogged } from "../resources/users";
|
|||||||
export type Auth = {
|
export type Auth = {
|
||||||
loggedUser: UserLogged | null;
|
loggedUser: UserLogged | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const AuthContext = createContext<Auth | undefined>(undefined)
|
const AuthContext = createContext<Auth | undefined>(undefined);
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { data: loggedUser, isLoading } = useCurrentUser();
|
const { data: loggedUser, isLoading } = useCurrentUser();
|
||||||
@@ -17,11 +17,7 @@ export function AuthProvider({ children }: {children: React.ReactNode}) {
|
|||||||
isLoading,
|
isLoading,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
<AuthContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAuth(): Auth {
|
export function useAuth(): Auth {
|
||||||
|
|||||||
16
frontend/src/services/resources/common.ts
Normal file
16
frontend/src/services/resources/common.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const ENTITY_NAMES = [
|
||||||
|
'contract',
|
||||||
|
'form',
|
||||||
|
'productor',
|
||||||
|
'product',
|
||||||
|
'shipment',
|
||||||
|
'user',
|
||||||
|
]
|
||||||
|
|
||||||
|
export type EntityName = (typeof ENTITY_NAMES)[number];
|
||||||
|
|
||||||
|
export type DeleteDependencies = {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
type: EntityName;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user