эндпоинт для получения сводной osv для группы компаний из всех osv
This commit is contained in:
parent
15a00ddafc
commit
cf77d1d51e
1
.gitignore
vendored
1
.gitignore
vendored
@ -330,6 +330,7 @@ docker-compose.yml
|
||||
.vscode
|
||||
my_project_env
|
||||
.cursorignore
|
||||
/test-scripts
|
||||
|
||||
# Graphify: generated graphs, caches, skill and local integration
|
||||
graphify-out/
|
||||
|
||||
@ -0,0 +1,137 @@
|
||||
"""add osv entries for group function
|
||||
|
||||
Revision ID: d614b414a32f
|
||||
Revises: c89d082850bd
|
||||
Create Date: 2026-09-17 17:15:16.421048
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'd614b414a32f'
|
||||
down_revision: Union[str, Sequence[str], None] = 'c89d082850bd'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("""
|
||||
CREATE OR REPLACE FUNCTION dkb.get_osv_entries_for_group(
|
||||
p_company_group_id uuid,
|
||||
p_period_year integer,
|
||||
p_period_quarter text
|
||||
)
|
||||
RETURNS TABLE (
|
||||
osv_entry_id uuid,
|
||||
recognition_job_id uuid,
|
||||
legal_entity_id uuid,
|
||||
stored_file_id uuid,
|
||||
account_code varchar,
|
||||
account_id uuid,
|
||||
account_root varchar,
|
||||
counterparty_name varchar,
|
||||
counterparty_inn varchar,
|
||||
counterparty_id uuid,
|
||||
opening_debit numeric,
|
||||
opening_credit numeric,
|
||||
turnover_debit numeric,
|
||||
turnover_credit numeric,
|
||||
closing_debit numeric,
|
||||
closing_credit numeric,
|
||||
legal_entity_name varchar,
|
||||
company_group_id uuid,
|
||||
company_group_name varchar,
|
||||
original_name varchar,
|
||||
file_status varchar,
|
||||
period_year smallint,
|
||||
period_quarter varchar,
|
||||
uploaded_at timestamptz,
|
||||
uploaded_by varchar,
|
||||
finished_at timestamptz,
|
||||
is_gsk boolean,
|
||||
is_gk boolean,
|
||||
entity_group_flag text
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
WITH classified_entries AS (
|
||||
SELECT
|
||||
oe.id AS osv_entry_id,
|
||||
oe.recognition_job_id,
|
||||
oe.legal_entity_id,
|
||||
oe.stored_file_id,
|
||||
CASE
|
||||
WHEN oe.account_id IS NOT NULL THEN coa.code
|
||||
ELSE oe.account_code
|
||||
END AS account_code,
|
||||
oe.account_id,
|
||||
oe.account_root,
|
||||
oe.counterparty_name,
|
||||
oe.counterparty_inn,
|
||||
oe.counterparty_id,
|
||||
oe.opening_debit,
|
||||
oe.opening_credit,
|
||||
oe.turnover_debit,
|
||||
oe.turnover_credit,
|
||||
oe.closing_debit,
|
||||
oe.closing_credit,
|
||||
le.name AS legal_entity_name,
|
||||
le.company_group_id,
|
||||
cg.name AS company_group_name,
|
||||
sf.original_name,
|
||||
sf.status AS file_status,
|
||||
sf.period_year,
|
||||
sf.period_quarter,
|
||||
sf.uploaded_at,
|
||||
sf.uploaded_by,
|
||||
rj.finished_at,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM dkb.related_group_member AS member
|
||||
JOIN dkb.related_group AS rg
|
||||
ON rg.id = member.related_group_id
|
||||
WHERE member.legal_entity_id = oe.legal_entity_id
|
||||
AND member.is_current IS TRUE
|
||||
AND rg.company_group_id = p_company_group_id
|
||||
AND rg.kind = 'GSK'
|
||||
) AS is_gsk,
|
||||
le.company_group_id = p_company_group_id AS is_gk
|
||||
FROM dkb.osv_entry AS oe
|
||||
LEFT JOIN dkb.chart_of_account AS coa ON coa.id = oe.account_id
|
||||
JOIN dkb.stored_file AS sf ON sf.id = oe.stored_file_id
|
||||
JOIN dkb.legal_entity AS le ON le.id = oe.legal_entity_id
|
||||
JOIN dkb.company_group AS cg ON cg.id = le.company_group_id
|
||||
LEFT JOIN dkb.recognition_job AS rj
|
||||
ON rj.id = oe.recognition_job_id
|
||||
AND rj.file_id = sf.id
|
||||
WHERE sf.status = 'UPLOADED'
|
||||
AND sf.period_year = p_period_year
|
||||
AND sf.period_quarter = p_period_quarter
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM dkb.company_group AS selected_group
|
||||
WHERE selected_group.id = p_company_group_id
|
||||
)
|
||||
)
|
||||
SELECT
|
||||
ce.*,
|
||||
CASE
|
||||
WHEN ce.is_gsk THEN 'ГСК'
|
||||
WHEN ce.is_gk THEN 'ГК'
|
||||
ELSE 'Внешний'
|
||||
END AS entity_group_flag
|
||||
FROM classified_entries AS ce;
|
||||
$function$;
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("""
|
||||
DROP FUNCTION IF EXISTS
|
||||
dkb.get_osv_entries_for_group(uuid, integer, text)
|
||||
""")
|
||||
@ -1,11 +1,28 @@
|
||||
from fastapi import APIRouter, File, HTTPException, UploadFile, Form, Depends
|
||||
from fastapi import APIRouter, File, HTTPException, UploadFile, Form, Depends, Query
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from src.api.dependencies.services import get_osv_service, OsvService
|
||||
from src.schemas.enums import PeriodQuarter
|
||||
from src.schemas.osv import OsvGroupEntryResponse
|
||||
import uuid
|
||||
|
||||
router = APIRouter(prefix='/osv', tags=['osv'])
|
||||
|
||||
|
||||
@router.get('/by-company-group', response_model=list[OsvGroupEntryResponse])
|
||||
async def get_osv_by_company_group(
|
||||
company_group_id: uuid.UUID,
|
||||
quarter: PeriodQuarter,
|
||||
year: int = Query(..., ge=1, le=9999),
|
||||
service: OsvService = Depends(get_osv_service),
|
||||
):
|
||||
"""Все ОСВ за период из UPLOADED-файлов с флагом владельца относительно ГК.
|
||||
|
||||
Пустая выборка (в том числе неизвестная ГК) возвращается как [].
|
||||
"""
|
||||
return await service.get_by_company_group(
|
||||
company_group_id=company_group_id, year=year, quarter=quarter
|
||||
)
|
||||
|
||||
@router.get('/by-file-id')
|
||||
async def get_osv_by_file(file_id: uuid.UUID, service: OsvService = Depends(get_osv_service)):
|
||||
result = await service.get_by_file_id(file_id)
|
||||
|
||||
@ -95,10 +95,10 @@ async def lifespan(app):
|
||||
app.state.storage = None
|
||||
app.state.nats = None
|
||||
try:
|
||||
smbclient.register_session(settings.OPENBAO.SMB.SMB_HOST, settings.OPENBAO.SMB.SMB_LOGIN, settings.OPENBAO.SMB.SMB_PASSWORD)
|
||||
smbclient.register_session(settings.OPENBAO.SMB.SMB_HOST, settings.OPENBAO.SMB.SMB_LOGIN, settings.OPENBAO.SMB.SMB_PASSWORD, port=settings.OPENBAO.SMB.SMB_PORT)
|
||||
except:
|
||||
logger.exception("Ошибка при регистрации сессии SMB")
|
||||
s3_client = Minio(settings.OPENBAO.MINIO.MINIO_URL, access_key=settings.OPENBAO.MINIO.MINIO_ADMIN_LOGIN, secret_key=settings.OPENBAO.MINIO.MINIO_ADMIN_PASSWORD, cert_check=False)
|
||||
s3_client = Minio(settings.OPENBAO.MINIO.MINIO_URL, access_key=settings.OPENBAO.MINIO.MINIO_ADMIN_LOGIN, secret_key=settings.OPENBAO.MINIO.MINIO_ADMIN_PASSWORD, secure=settings.OPENBAO.MINIO.MINIO_SECURE, cert_check=False)
|
||||
try:
|
||||
bucket_exists = await s3_client.bucket_exists(settings.OPENBAO.MINIO.MINIO_BUCKET)
|
||||
logger.info("Бакет проверен. bucket_exists={bucket_exists}", bucket_exists=bucket_exists)
|
||||
@ -247,4 +247,4 @@ class SPAStaticFiles(StaticFiles):
|
||||
|
||||
app.mount("/", SPAStaticFiles(directory="web", html=True), name="web")
|
||||
|
||||
app.mount("/", StaticFiles(directory="web", html=True), name="web")
|
||||
app.mount("/", StaticFiles(directory="web", html=True), name="web")
|
||||
|
||||
@ -65,7 +65,7 @@ class PDFPollWorker:
|
||||
logger.exception("Не получилось достать данные из кэша")
|
||||
jobs = []
|
||||
|
||||
all_files = smbclient.listdir(self.smb_path)
|
||||
all_files = smbclient.listdir(self.smb_path, port=settings.OPENBAO.SMB.SMB_PORT)
|
||||
|
||||
for job in jobs:
|
||||
search_fname = f"{job.file_id}"
|
||||
@ -85,8 +85,8 @@ class PDFPollWorker:
|
||||
|
||||
logger.info("Начинаю обработку файла по пути smb_dir={smb_dir}", smb_dir=new_dir)
|
||||
files = []
|
||||
for fname in smbclient.listdir(new_dir):
|
||||
with smbclient.open_file(f'{new_dir}/{fname}', 'r') as f:
|
||||
for fname in smbclient.listdir(new_dir, port=settings.OPENBAO.SMB.SMB_PORT):
|
||||
with smbclient.open_file(f'{new_dir}/{fname}', 'r', port=settings.OPENBAO.SMB.SMB_PORT) as f:
|
||||
files.append(json.loads(f.read()))
|
||||
s3_json_dict = {
|
||||
'file_id': job.file_id,
|
||||
|
||||
@ -108,7 +108,7 @@ class PollWorker:
|
||||
|
||||
if row['subject'] == 'file.img_uploaded':
|
||||
last_part = row['row_payload']['s3_key'].split('/')[-1]
|
||||
with smbclient.open_file(f'{settings.OPENBAO.SMB.SMB_PATH}/Файлы/{last_part}', 'wb') as f:
|
||||
with smbclient.open_file(f'{settings.OPENBAO.SMB.SMB_PATH}/Файлы/{last_part}', 'wb', port=settings.OPENBAO.SMB.SMB_PORT) as f:
|
||||
data = await self.storage.get_object(row['row_payload']['s3_key'])
|
||||
content = await data.read()
|
||||
f.write(content)
|
||||
@ -177,4 +177,4 @@ class PollWorker:
|
||||
file_stmt = update(StoredFile).where(StoredFile.current_recognition_job_id == row_id).values(status=None)
|
||||
await session.execute(stmt)
|
||||
await session.execute(file_stmt)
|
||||
await session.commit()
|
||||
await session.commit()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@ -5,6 +6,7 @@ class DBSettings(BaseSettings):
|
||||
DATABASE_URL: str
|
||||
|
||||
class MinioSettings(BaseSettings):
|
||||
MINIO_SECURE: bool = True
|
||||
MINIO_URL: str
|
||||
MINIO_ADMIN_LOGIN: str
|
||||
MINIO_ADMIN_PASSWORD: str
|
||||
@ -16,6 +18,7 @@ class NATSSettings(BaseSettings):
|
||||
NATS_JWT: str
|
||||
|
||||
class SMBSettings(BaseSettings):
|
||||
SMB_PORT: int = Field(default=445, ge=1, le=65535)
|
||||
SMB_HOST: str
|
||||
SMB_PATH: str
|
||||
SMB_LOGIN: str
|
||||
@ -47,4 +50,4 @@ class Settings(BaseSettings):
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
settings = get_settings()
|
||||
settings = get_settings()
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
from .base_repository import BaseRepository
|
||||
from src.models import OsvEntry
|
||||
from sqlalchemy import insert, select
|
||||
from sqlalchemy import insert, select, text
|
||||
|
||||
class OsvRepository(BaseRepository):
|
||||
model = OsvEntry
|
||||
|
||||
async def get_by_company_group(self, company_group_id, year, quarter):
|
||||
stmt = text("""
|
||||
SELECT * FROM dkb.get_osv_entries_for_group(
|
||||
CAST(:company_group_id AS uuid),
|
||||
CAST(:year AS integer),
|
||||
CAST(:quarter AS text)
|
||||
)
|
||||
""")
|
||||
result = await self.session.execute(stmt, {
|
||||
"company_group_id": company_group_id,
|
||||
"year": year,
|
||||
"quarter": quarter,
|
||||
})
|
||||
return [dict(row) for row in result.mappings().all()]
|
||||
|
||||
async def get_by_file_id(self, file_id):
|
||||
stmt = select(self.model).where(self.model.stored_file_id == file_id)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
return result.scalars().all()
|
||||
|
||||
42
api/src/schemas/osv.py
Normal file
42
api/src/schemas/osv.py
Normal file
@ -0,0 +1,42 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.schemas.enums import PeriodQuarter
|
||||
|
||||
|
||||
class OsvGroupEntryResponse(BaseModel):
|
||||
"""Строка get_osv_entries_for_group; денежные суммы в JSON — точные строки."""
|
||||
|
||||
osv_entry_id: UUID
|
||||
recognition_job_id: UUID
|
||||
legal_entity_id: UUID
|
||||
stored_file_id: UUID
|
||||
account_code: str | None
|
||||
account_id: UUID | None
|
||||
account_root: str
|
||||
counterparty_name: str
|
||||
counterparty_inn: str
|
||||
counterparty_id: UUID | None
|
||||
opening_debit: Decimal
|
||||
opening_credit: Decimal
|
||||
turnover_debit: Decimal
|
||||
turnover_credit: Decimal
|
||||
closing_debit: Decimal
|
||||
closing_credit: Decimal
|
||||
legal_entity_name: str
|
||||
company_group_id: UUID
|
||||
company_group_name: str
|
||||
original_name: str
|
||||
file_status: str
|
||||
period_year: int
|
||||
period_quarter: PeriodQuarter
|
||||
uploaded_at: datetime | None
|
||||
uploaded_by: str | None
|
||||
finished_at: datetime | None
|
||||
is_gsk: bool
|
||||
is_gk: bool
|
||||
entity_group_flag: Literal['ГСК', 'ГК', 'Внешний']
|
||||
@ -4,11 +4,21 @@ from fastapi import UploadFile
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from src.common.exceptions import NotFoundError
|
||||
from src.repositories.osv_repository import OsvEntry, OsvRepository
|
||||
from src.schemas.enums import PeriodQuarter
|
||||
from src.schemas.osv import OsvGroupEntryResponse
|
||||
|
||||
class OsvService:
|
||||
def __init__(self, session: AsyncSession, osv_repo: OsvRepository):
|
||||
self.osv_repo = osv_repo
|
||||
self.session = session
|
||||
|
||||
async def get_by_company_group(
|
||||
self, company_group_id: uuid.UUID, year: int, quarter: PeriodQuarter
|
||||
) -> list[OsvGroupEntryResponse]:
|
||||
entries = await self.osv_repo.get_by_company_group(
|
||||
company_group_id=company_group_id, year=year, quarter=quarter.value
|
||||
)
|
||||
return [OsvGroupEntryResponse.model_validate(entry) for entry in entries]
|
||||
|
||||
|
||||
async def get_by_file_id(self, file_id: uuid.UUID):
|
||||
@ -18,4 +28,4 @@ class OsvService:
|
||||
raise NotFoundError('No osv entries found')
|
||||
return osv_entries
|
||||
except Exception as e:
|
||||
raise
|
||||
raise
|
||||
|
||||
287
docs/database-er-diagram-17-09-2026.md
Normal file
287
docs/database-er-diagram-17-09-2026.md
Normal file
@ -0,0 +1,287 @@
|
||||
# ER-диаграмма схемы `dkb`
|
||||
|
||||
Диаграмма построена по SQLAlchemy-моделям и Alembic-миграциям. `PK` — первичный ключ, `FK` — внешний ключ, `UK` — уникальное поле. Все таблицы находятся в PostgreSQL-схеме `dkb`.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CHART_OF_ACCOUNT {
|
||||
uuid id PK
|
||||
string code UK
|
||||
uuid parent_id FK "nullable"
|
||||
string name
|
||||
enum kind "A | P | AP"
|
||||
}
|
||||
|
||||
REGIONAL_OFFICE {
|
||||
uuid id PK
|
||||
string name
|
||||
boolean is_active
|
||||
}
|
||||
|
||||
COMPANY_GROUP {
|
||||
uuid id PK
|
||||
string crm_id UK
|
||||
string name
|
||||
uuid base_regional_office_id FK
|
||||
datetime valid_from
|
||||
string updated_by
|
||||
datetime created_at
|
||||
}
|
||||
|
||||
COMPANY_GROUP_HISTORY {
|
||||
uuid id PK
|
||||
uuid company_group_id FK
|
||||
string crm_id UK
|
||||
string name
|
||||
uuid base_regional_office_id "snapshot, not FK"
|
||||
datetime valid_from
|
||||
datetime valid_to
|
||||
string updated_by
|
||||
}
|
||||
|
||||
LEGAL_ENTITY {
|
||||
uuid id PK
|
||||
string inn UK
|
||||
enum entity_type "IP | UL"
|
||||
string name
|
||||
uuid company_group_id FK
|
||||
uuid regional_office_id FK
|
||||
date close_date
|
||||
string updated_by
|
||||
datetime created_at
|
||||
}
|
||||
|
||||
LEGAL_ENTITY_HISTORY {
|
||||
uuid id PK
|
||||
uuid legal_entity_id FK
|
||||
string inn UK
|
||||
enum entity_type "IP | UL"
|
||||
string name
|
||||
uuid company_group_id FK
|
||||
uuid regional_office_id FK
|
||||
date close_date
|
||||
datetime valid_from
|
||||
datetime valid_to
|
||||
string changed_by
|
||||
}
|
||||
|
||||
LEGAL_ENTITY_ALIAS {
|
||||
uuid id PK
|
||||
uuid legal_entity_id FK
|
||||
string alias
|
||||
string normalized_alias
|
||||
string created_by
|
||||
datetime created_at
|
||||
}
|
||||
|
||||
RELATED_GROUP {
|
||||
uuid id PK
|
||||
string code UK
|
||||
uuid company_group_id FK
|
||||
enum kind "GSK | GSZ"
|
||||
string name
|
||||
date created_date
|
||||
}
|
||||
|
||||
RELATED_GROUP_MEMBER {
|
||||
uuid id PK
|
||||
uuid related_group_id FK
|
||||
uuid legal_entity_id FK
|
||||
enum kind "GSK | GSZ"
|
||||
datetime valid_from
|
||||
datetime valid_to
|
||||
boolean is_current
|
||||
string created_by
|
||||
}
|
||||
|
||||
CONSOLIDATION {
|
||||
uuid id PK
|
||||
uuid related_group_id FK
|
||||
smallint year
|
||||
enum quarter "Q1 | Q2 | Q3 | Q4"
|
||||
datetime created_at
|
||||
}
|
||||
|
||||
CONSOLIDATION_VERSION {
|
||||
uuid id PK
|
||||
uuid consolidation_id FK
|
||||
string version_no
|
||||
date consolidation_date
|
||||
string initiator
|
||||
string approved_in_rf
|
||||
string verified_in_go
|
||||
boolean kfo_confirmation_enabled
|
||||
boolean is_in_work
|
||||
datetime created_at
|
||||
string created_by
|
||||
}
|
||||
|
||||
CONSOLIDATION_MEMBER {
|
||||
uuid id PK
|
||||
uuid consolidation_version_id FK
|
||||
uuid legal_entity_id FK
|
||||
}
|
||||
|
||||
CORRECTION {
|
||||
uuid id PK
|
||||
uuid consolidation_version_id FK
|
||||
uuid correction_id FK "nullable; parent correction"
|
||||
uuid legal_entity_id FK "nullable"
|
||||
string debit_line_code
|
||||
string debit_account_code
|
||||
numeric debit_adjustment
|
||||
string credit_line_code
|
||||
string credit_account_code
|
||||
numeric credit_adjustment
|
||||
numeric deviation_amount
|
||||
numeric deviation_percent
|
||||
text comment
|
||||
string created_by
|
||||
datetime created_at
|
||||
string uploaded_by
|
||||
datetime uploaded_at
|
||||
}
|
||||
|
||||
STORED_FILE {
|
||||
uuid id PK
|
||||
uuid legal_entity_id FK
|
||||
enum document_type "nullable"
|
||||
enum file_type "XLSX | XLS | PDF | PNG | JPG"
|
||||
string original_name
|
||||
string s3_key UK "nullable"
|
||||
string content_hash "nullable"
|
||||
bigint size_bytes
|
||||
smallint period_year
|
||||
enum period_quarter "nullable"
|
||||
enum status "nullable"
|
||||
uuid current_recognition_job_id FK "nullable"
|
||||
string uploaded_by "nullable"
|
||||
datetime uploaded_at "nullable"
|
||||
}
|
||||
|
||||
RECOGNITION_JOB {
|
||||
uuid id PK
|
||||
uuid file_id FK
|
||||
uuid legal_entity_id FK
|
||||
enum pipeline "nullable"
|
||||
string service_version "nullable"
|
||||
enum status "nullable"
|
||||
smallint attempt
|
||||
string queue_msg_id "nullable"
|
||||
datetime requested_at "nullable"
|
||||
datetime started_at "nullable"
|
||||
datetime finished_at "nullable"
|
||||
text error_message "nullable"
|
||||
string raw_result_s3_key "nullable"
|
||||
}
|
||||
|
||||
FINANCIAL_STATEMENT_META {
|
||||
uuid id PK
|
||||
uuid stored_file_id FK
|
||||
enum statement_type "BALANCE | INCOME"
|
||||
string inn
|
||||
boolean inn_verified
|
||||
date report_date "nullable"
|
||||
boolean report_date_verified
|
||||
string units
|
||||
boolean units_verified
|
||||
}
|
||||
|
||||
FINANCIAL_STATEMENT_LINE {
|
||||
uuid id PK
|
||||
uuid recognition_job_id FK
|
||||
uuid meta_id FK "nullable"
|
||||
uuid legal_entity_id FK
|
||||
string line_code
|
||||
boolean line_code_recognized
|
||||
string line_name
|
||||
boolean line_name_recognized
|
||||
enum row_kind
|
||||
bigint ordinal
|
||||
}
|
||||
|
||||
FINANCIAL_STATEMENT_VALUE {
|
||||
uuid id PK
|
||||
uuid line_id FK
|
||||
numeric amount "nullable"
|
||||
string string_value "nullable"
|
||||
smallint column_index
|
||||
boolean is_recognized
|
||||
}
|
||||
|
||||
OSV_ENTRY {
|
||||
uuid id PK
|
||||
uuid recognition_job_id FK
|
||||
uuid legal_entity_id FK
|
||||
uuid stored_file_id FK
|
||||
string account_code
|
||||
uuid account_id FK
|
||||
string account_root
|
||||
string counterparty_name
|
||||
string counterparty_inn
|
||||
uuid counterparty_id FK "nullable"
|
||||
numeric opening_debit
|
||||
numeric opening_credit
|
||||
numeric turnover_debit
|
||||
numeric turnover_credit
|
||||
numeric closing_debit
|
||||
numeric closing_credit
|
||||
}
|
||||
|
||||
CHART_OF_ACCOUNT o|--o{ CHART_OF_ACCOUNT : "parent_id"
|
||||
REGIONAL_OFFICE ||--o{ COMPANY_GROUP : "base office"
|
||||
COMPANY_GROUP ||--o{ COMPANY_GROUP_HISTORY : "history"
|
||||
COMPANY_GROUP ||--o{ LEGAL_ENTITY : "contains"
|
||||
REGIONAL_OFFICE ||--o{ LEGAL_ENTITY : "serves"
|
||||
LEGAL_ENTITY ||--o{ LEGAL_ENTITY_ALIAS : "aliases"
|
||||
LEGAL_ENTITY ||--o{ LEGAL_ENTITY_HISTORY : "history"
|
||||
COMPANY_GROUP ||--o{ LEGAL_ENTITY_HISTORY : "group snapshot"
|
||||
REGIONAL_OFFICE ||--o{ LEGAL_ENTITY_HISTORY : "office snapshot"
|
||||
|
||||
COMPANY_GROUP ||--o{ RELATED_GROUP : "owns"
|
||||
RELATED_GROUP ||--o{ RELATED_GROUP_MEMBER : "memberships"
|
||||
LEGAL_ENTITY ||--o{ RELATED_GROUP_MEMBER : "participates"
|
||||
RELATED_GROUP ||--o{ CONSOLIDATION : "periods"
|
||||
CONSOLIDATION ||--o{ CONSOLIDATION_VERSION : "versions"
|
||||
CONSOLIDATION_VERSION ||--o{ CONSOLIDATION_MEMBER : "members"
|
||||
LEGAL_ENTITY ||--o{ CONSOLIDATION_MEMBER : "included"
|
||||
CONSOLIDATION_VERSION ||--o{ CORRECTION : "corrections"
|
||||
LEGAL_ENTITY o|--o{ CORRECTION : "entity correction"
|
||||
CORRECTION o|--o{ CORRECTION : "parent correction"
|
||||
|
||||
LEGAL_ENTITY ||--o{ STORED_FILE : "files"
|
||||
STORED_FILE ||--o{ RECOGNITION_JOB : "recognition attempts"
|
||||
LEGAL_ENTITY ||--o{ RECOGNITION_JOB : "recognized for"
|
||||
RECOGNITION_JOB o|--o{ STORED_FILE : "current job"
|
||||
STORED_FILE ||--o{ FINANCIAL_STATEMENT_META : "metadata"
|
||||
FINANCIAL_STATEMENT_META o|--o{ FINANCIAL_STATEMENT_LINE : "lines"
|
||||
RECOGNITION_JOB ||--o{ FINANCIAL_STATEMENT_LINE : "produces"
|
||||
LEGAL_ENTITY ||--o{ FINANCIAL_STATEMENT_LINE : "owns"
|
||||
FINANCIAL_STATEMENT_LINE ||--o{ FINANCIAL_STATEMENT_VALUE : "values"
|
||||
|
||||
RECOGNITION_JOB ||--o{ OSV_ENTRY : "produces"
|
||||
STORED_FILE ||--o{ OSV_ENTRY : "contains"
|
||||
LEGAL_ENTITY ||--o{ OSV_ENTRY : "owner"
|
||||
LEGAL_ENTITY o|--o{ OSV_ENTRY : "counterparty"
|
||||
CHART_OF_ACCOUNT ||--o{ OSV_ENTRY : "account"
|
||||
```
|
||||
|
||||
## Важные ограничения
|
||||
|
||||
- `consolidation`: уникальна комбинация (`related_group_id`, `year`, `quarter`).
|
||||
- `consolidation_version`: уникальна пара (`consolidation_id`, `version_no`).
|
||||
- `consolidation_member`: уникальна пара (`consolidation_version_id`, `legal_entity_id`).
|
||||
- `legal_entity_history`: уникальна пара (`legal_entity_id`, `valid_from`).
|
||||
- `related_group_member`: действуют частичные уникальные индексы для текущих участников.
|
||||
- `stored_file` и `recognition_job` связаны циклически: файл хранит текущую попытку, а каждая попытка ссылается на файл.
|
||||
|
||||
## Представление
|
||||
|
||||
`dkb.v_osv_entry_with_file_flags` объединяет `osv_entry`, `stored_file`, `recognition_job`, `legal_entity`, `related_group_member` и `related_group`. Оно добавляет вычисляемый тип контрагента: `ГСК`, `ГК` или `Внешний`.
|
||||
|
||||
## Источники
|
||||
|
||||
- `api/src/models/*.py`
|
||||
- `api/alembic/versions/1c4e0a7d9612_first_migration.py`
|
||||
- `api/alembic/versions/c89d082850bd_add_default_and_nullability.py`
|
||||
- `api/scripts/create_osv_entry_view.sql`
|
||||
Loading…
x
Reference in New Issue
Block a user