delete-from-cell #132
@ -16,7 +16,7 @@ script_location = %(here)s/alembic
|
|||||||
# sys.path path, will be prepended to sys.path if present.
|
# sys.path path, will be prepended to sys.path if present.
|
||||||
# defaults to the current working directory. for multiple paths, the path separator
|
# defaults to the current working directory. for multiple paths, the path separator
|
||||||
# is defined by "path_separator" below.
|
# is defined by "path_separator" below.
|
||||||
prepend_sys_path = .
|
prepend_sys_path = %(here)s:%(here)s/alembic
|
||||||
|
|
||||||
|
|
||||||
# timezone to use when rendering the date within the migration file
|
# timezone to use when rendering the date within the migration file
|
||||||
|
|||||||
44
api/alembic/sql_migration.py
Normal file
44
api/alembic/sql_migration.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
||||||
|
_SQL_DIRECTORY = Path(__file__).parent / "versions" / "sql"
|
||||||
|
|
||||||
|
|
||||||
|
def _split_statements(sql: str) -> list[str]:
|
||||||
|
statements: list[str] = []
|
||||||
|
current: list[str] = []
|
||||||
|
dollar_tag: str | None = None
|
||||||
|
|
||||||
|
for line in sql.splitlines():
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped.startswith("--"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dollar_tag is None:
|
||||||
|
match = _DOLLAR_TAG_RE.search(stripped)
|
||||||
|
if match:
|
||||||
|
dollar_tag = match.group(0)
|
||||||
|
elif stripped.startswith(dollar_tag):
|
||||||
|
dollar_tag = None
|
||||||
|
|
||||||
|
current.append(line)
|
||||||
|
if dollar_tag is None and stripped.endswith(";"):
|
||||||
|
statements.append("\n".join(current))
|
||||||
|
current = []
|
||||||
|
|
||||||
|
if remaining := "\n".join(current).strip():
|
||||||
|
statements.append(remaining)
|
||||||
|
|
||||||
|
return statements
|
||||||
|
|
||||||
|
|
||||||
|
def run_sql_file_migration(filename: str) -> None:
|
||||||
|
"""Execute statements from a SQL file in alembic/versions/sql."""
|
||||||
|
sql = (_SQL_DIRECTORY / filename).read_text(encoding="utf-8")
|
||||||
|
for statement in _split_statements(sql):
|
||||||
|
if stripped := statement.rstrip(";").strip():
|
||||||
|
op.execute(stripped)
|
||||||
@ -1,78 +1,12 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
revision = "0002"
|
revision = "0002"
|
||||||
down_revision = "0001"
|
down_revision = "0001"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> Optional[str]:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> List[str]:
|
|
||||||
statements: List[str] = []
|
|
||||||
current: List[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: Optional[str] = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0002_functions.sql")
|
run_sql_file_migration("0002_functions.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,78 +1,13 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
revision = "0006"
|
revision = "0006"
|
||||||
down_revision = "0005"
|
down_revision = "0005"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> Optional[str]:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> List[str]:
|
|
||||||
statements: List[str] = []
|
|
||||||
current: List[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: Optional[str] = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0006_form3.sql")
|
run_sql_file_migration("0006_form3.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0010"
|
revision = "0010"
|
||||||
down_revision = "0009"
|
down_revision = "0009"
|
||||||
@ -12,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0010_form4.sql")
|
run_sql_file_migration("0010_form4.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0011"
|
revision = "0011"
|
||||||
down_revision = "0010"
|
down_revision = "0010"
|
||||||
@ -12,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0011_form3_log.sql")
|
run_sql_file_migration("0011_form3_log.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0012"
|
revision = "0012"
|
||||||
down_revision = "0011"
|
down_revision = "0011"
|
||||||
@ -12,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0012_del_project.sql")
|
run_sql_file_migration("0012_del_project.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0013"
|
revision = "0013"
|
||||||
down_revision = "0012"
|
down_revision = "0012"
|
||||||
@ -12,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0013_form2_limit.sql")
|
run_sql_file_migration("0013_form2_limit.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0014"
|
revision = "0014"
|
||||||
down_revision = "0013"
|
down_revision = "0013"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0014_direction.sql")
|
run_sql_file_migration("0014_direction.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0015"
|
revision = "0015"
|
||||||
down_revision = "0014"
|
down_revision = "0014"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0015_aggregates_fix.sql")
|
run_sql_file_migration("0015_aggregates_fix.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0016"
|
revision = "0016"
|
||||||
down_revision = "0015"
|
down_revision = "0015"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0016_project_fields.sql")
|
run_sql_file_migration("0016_project_fields.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0017"
|
revision = "0017"
|
||||||
down_revision = "0016"
|
down_revision = "0016"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0017_project_summary.sql")
|
run_sql_file_migration("0017_project_summary.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0018"
|
revision = "0018"
|
||||||
down_revision = "0017"
|
down_revision = "0017"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0018_technical_number.sql")
|
run_sql_file_migration("0018_technical_number.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0020"
|
revision = "0020"
|
||||||
down_revision = "0019"
|
down_revision = "0019"
|
||||||
@ -10,72 +6,8 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0020_carryover_form3.sql")
|
run_sql_file_migration("0020_carryover_form3.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import os
|
from sql_migration import run_sql_file_migration
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0021"
|
revision = "0021"
|
||||||
down_revision = "0020"
|
down_revision = "0020"
|
||||||
@ -10,73 +6,9 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _find_dollar_tag(line: str) -> str | None:
|
|
||||||
m = _DOLLAR_TAG_RE.search(line.strip())
|
|
||||||
return m.group(0) if m else None
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.split("\n"):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar:
|
|
||||||
tag = _find_dollar_tag(stripped)
|
|
||||||
if tag and tag.endswith("$") and tag.startswith("$"):
|
|
||||||
dollar_tag = tag
|
|
||||||
in_dollar = True
|
|
||||||
current.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if in_dollar and dollar_tag and stripped.startswith(dollar_tag):
|
|
||||||
after = stripped[len(dollar_tag):].strip()
|
|
||||||
if after == ";" or after == "":
|
|
||||||
in_dollar = False
|
|
||||||
dollar_tag = None
|
|
||||||
if after == ";":
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_dollar and stripped.rstrip().endswith(";"):
|
|
||||||
current.append(line)
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
|
|
||||||
remaining = "\n".join(current).strip()
|
|
||||||
if remaining:
|
|
||||||
statements.append(remaining)
|
|
||||||
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0021_summary_views.sql")
|
run_sql_file_migration("0021_summary_views.sql")
|
||||||
with open(ddl_path) as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
statements = _split_statements(content)
|
|
||||||
for stmt in statements:
|
|
||||||
stripped = stmt.strip().rstrip(";").strip()
|
|
||||||
if not stripped:
|
|
||||||
continue
|
|
||||||
if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")):
|
|
||||||
continue
|
|
||||||
op.execute(stripped)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|
||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
revision = "0022"
|
revision = "0022"
|
||||||
down_revision = "0021"
|
down_revision = "0021"
|
||||||
@ -10,52 +8,9 @@ branch_labels = None
|
|||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
|
||||||
|
|
||||||
|
|
||||||
def _split_statements(sql: str) -> list[str]:
|
|
||||||
statements: list[str] = []
|
|
||||||
current: list[str] = []
|
|
||||||
dollar_tag: str | None = None
|
|
||||||
|
|
||||||
for line in sql.splitlines():
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("--"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if dollar_tag is None:
|
|
||||||
match = _DOLLAR_TAG_RE.search(stripped)
|
|
||||||
if match:
|
|
||||||
dollar_tag = match.group(0)
|
|
||||||
elif stripped.startswith(dollar_tag):
|
|
||||||
dollar_tag = None
|
|
||||||
|
|
||||||
current.append(line)
|
|
||||||
if dollar_tag is None and stripped.endswith(";"):
|
|
||||||
statements.append("\n".join(current))
|
|
||||||
current = []
|
|
||||||
|
|
||||||
if remaining := "\n".join(current).strip():
|
|
||||||
statements.append(remaining)
|
|
||||||
return statements
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
ddl_path = os.path.join(
|
run_sql_file_migration("0022_summary2_contract_rows.sql")
|
||||||
os.path.dirname(__file__), "sql", "0022_summary2_contract_rows.sql"
|
|
||||||
)
|
|
||||||
with open(ddl_path) as ddl_file:
|
|
||||||
statements = _split_statements(ddl_file.read())
|
|
||||||
for statement in statements:
|
|
||||||
op.execute(statement.rstrip(";").strip())
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer, varchar, varchar)")
|
pass
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer, varchar, varchar)")
|
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer)")
|
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer)")
|
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[])")
|
|
||||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[])")
|
|
||||||
op.execute("DROP INDEX IF EXISTS v3.ix_v3_budget_line_svod2")
|
|
||||||
op.execute("DROP INDEX IF EXISTS v3.ix_v3_budget_form_svod2")
|
|
||||||
|
|||||||
16
api/alembic/versions/0023_apply_cell_empty_string.py
Normal file
16
api/alembic/versions/0023_apply_cell_empty_string.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from alembic import op
|
||||||
|
|
||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
revision = "0023"
|
||||||
|
down_revision = "0022"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
run_sql_file_migration("0023_apply_cell_empty_string.sql")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
555
api/alembic/versions/sql/0023_apply_cell_empty_string.sql
Normal file
555
api/alembic/versions/sql/0023_apply_cell_empty_string.sql
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
-- DROP FUNCTION v3._apply_form_cell(int4, text, jsonb, varchar, int4);
|
||||||
|
|
||||||
|
-- DROP FUNCTION v3._apply_form_cell(int4, text, jsonb, varchar, int4);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION v3._apply_form_cell(p_line_id integer, p_column text, p_value jsonb, p_sheet character varying DEFAULT NULL::character varying, p_form_id integer DEFAULT NULL::integer)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
v_parts TEXT[];
|
||||||
|
v_scope TEXT;
|
||||||
|
v_field TEXT;
|
||||||
|
v_form_type TEXT;
|
||||||
|
v_q SMALLINT;
|
||||||
|
v_actor TEXT;
|
||||||
|
v_target_table TEXT;
|
||||||
|
v_target_col TEXT;
|
||||||
|
v_target_type TEXT;
|
||||||
|
v_key_kind TEXT;
|
||||||
|
v_str TEXT;
|
||||||
|
v_sql TEXT;
|
||||||
|
v_sat_table TEXT;
|
||||||
|
v_cnt INT;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
v_parts := string_to_array(p_column, '.');
|
||||||
|
|
||||||
|
IF p_sheet = 'OTCH9F' THEN
|
||||||
|
DECLARE v_far_month SMALLINT;
|
||||||
|
BEGIN
|
||||||
|
SELECT month INTO v_far_month FROM v3.fixed_asset_report WHERE id = p_line_id;
|
||||||
|
IF v_far_month IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'fixed_asset_report.id=% не существует', p_line_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF array_length(v_parts,1) = 1 THEN
|
||||||
|
IF v_parts[1] = 'equipment_name' THEN
|
||||||
|
RAISE EXCEPTION 'normative_field: equipment_name (нормативная номенклатура, правится отдельным API)';
|
||||||
|
ELSIF v_parts[1] IN ('month','expense_item_id','item_id','section_code','id') THEN
|
||||||
|
RAISE EXCEPTION 'key_field: % (часть ключа строки, RO)', v_parts[1];
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF array_length(v_parts,1) <> 2 THEN
|
||||||
|
RAISE EXCEPTION 'bad_column_format: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_parts[1] = 'totals' THEN
|
||||||
|
IF v_parts[2] = 'go_balance_only' THEN
|
||||||
|
v_target_col := 'go_balance_only_amt'; v_target_type := 'NUMERIC';
|
||||||
|
ELSIF v_parts[2] IN ('acquired_total','disposed_total','balance_qty','balance_amt') THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: totals.%', v_parts[2];
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
ELSIF v_parts[1] IN ('b_604','b_60415') THEN
|
||||||
|
IF v_parts[2] LIKE 'opening_%' AND v_far_month <> 1 THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: % (opening для month>1 вычисляется из base + накопит. дельты)', p_column;
|
||||||
|
END IF;
|
||||||
|
IF v_parts[1] = 'b_604' THEN
|
||||||
|
v_target_col := CASE v_parts[2]
|
||||||
|
WHEN 'opening_qty' THEN 'opening_qty_604'
|
||||||
|
WHEN 'opening_amt' THEN 'opening_amt_604'
|
||||||
|
WHEN 'acquired_qty' THEN 'acquired_qty_604'
|
||||||
|
WHEN 'acquired_amt' THEN 'acquired_amt_604'
|
||||||
|
WHEN 'disposed_qty' THEN 'disposed_qty_604'
|
||||||
|
WHEN 'disposed_amt' THEN 'disposed_amt_604'
|
||||||
|
END;
|
||||||
|
IF v_target_col IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END;
|
||||||
|
ELSE -- b_60415
|
||||||
|
v_target_col := CASE v_parts[2]
|
||||||
|
WHEN 'opening_qty' THEN 'opening_qty_60415'
|
||||||
|
WHEN 'opening_amt' THEN 'opening_amt_60415'
|
||||||
|
WHEN 'acquired_qty' THEN 'acquired_qty_60415'
|
||||||
|
WHEN 'acquired_amt' THEN 'acquired_amt_60415'
|
||||||
|
WHEN 'transferred_qty' THEN 'transferred_qty_60415'
|
||||||
|
WHEN 'transferred_amt' THEN 'transferred_amt_60415'
|
||||||
|
END;
|
||||||
|
IF v_target_col IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END;
|
||||||
|
END IF;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE format(
|
||||||
|
'UPDATE v3.fixed_asset_report SET %I = ($2 #>> ''{}'')::%s WHERE id = $1',
|
||||||
|
v_target_col, v_target_type
|
||||||
|
) USING p_line_id, p_value;
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF p_sheet = 'AHR_LIMIT' THEN
|
||||||
|
IF p_form_id IS NULL THEN
|
||||||
|
RAISE EXCEPTION 'internal: p_form_id не передан для AHR_LIMIT';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM v3.limit_template lt
|
||||||
|
WHERE lt.id = p_line_id AND lt.row_type = 'LEAF') THEN
|
||||||
|
RAISE EXCEPTION 'limit_template.id=% не существует или не LEAF (SECTION/GROUP не редактируется)', p_line_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF array_length(v_parts,1) <> 1 THEN
|
||||||
|
RAISE EXCEPTION 'bad_column_format: %, expected single key', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CASE v_parts[1]
|
||||||
|
WHEN 'qty_q1','qty_q2','qty_q3','qty_q4' THEN
|
||||||
|
v_target_col := v_parts[1]; v_target_type := 'INTEGER';
|
||||||
|
WHEN 'comment' THEN
|
||||||
|
v_target_col := 'comment'; v_target_type := 'TEXT';
|
||||||
|
WHEN 'amount_q1','amount_q2','amount_q3','amount_q4' THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: % (вычисляется из qty × limit × period_factor)', p_column;
|
||||||
|
WHEN 'name','unit','section_no','expense_item_code',
|
||||||
|
'limit_with_vat','limit_without_vat' THEN
|
||||||
|
RAISE EXCEPTION 'normative_field: % (нормативный справочник, правится админом отдельно)', p_column;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
IF (v_target_type = 'NUMERIC' OR v_target_type = 'INTEGER') AND p_value = '""'::jsonb THEN
|
||||||
|
p_value := '"0"'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE format(
|
||||||
|
'INSERT INTO v3.form_limit (budget_form_id, template_id, %1$I) '
|
||||||
|
|| 'VALUES ($1, $2, ($3 #>> ''{}'')::%2$s) '
|
||||||
|
|| 'ON CONFLICT (budget_form_id, template_id) DO UPDATE SET %1$I = EXCLUDED.%1$I',
|
||||||
|
v_target_col, v_target_type
|
||||||
|
) USING p_form_id, p_line_id, p_value;
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN
|
||||||
|
v_sat_table := CASE p_sheet
|
||||||
|
WHEN 'AHR_RENT' THEN 'rent_detail'
|
||||||
|
WHEN 'AHR_UTILITY' THEN 'utility_detail'
|
||||||
|
WHEN 'AHR_SECURITY' THEN 'security_detail'
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF array_length(v_parts,1) = 1 THEN
|
||||||
|
CASE v_parts[1]
|
||||||
|
WHEN 'contract_number' THEN v_target_col := 'contract_number'; v_target_type := 'TEXT';
|
||||||
|
WHEN 'contract_end_date' THEN v_target_col := 'contract_end_date'; v_target_type := 'DATE';
|
||||||
|
WHEN 'comment' THEN v_target_col := 'comment'; v_target_type := 'TEXT';
|
||||||
|
WHEN 'address','object_type','rented_area','object_area' THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: % (атрибут v3.vsp, правится отдельно)', p_column;
|
||||||
|
ELSE RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END CASE;
|
||||||
|
ELSIF array_length(v_parts,1) = 2 AND v_parts[1] = 'plan' THEN
|
||||||
|
IF v_parts[2] = 'year' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF;
|
||||||
|
IF v_parts[2] NOT IN ('q1','q2','q3','q4') THEN RAISE EXCEPTION 'unknown_column: %', p_column; END IF;
|
||||||
|
v_target_col := 'plan_' || v_parts[2];
|
||||||
|
v_target_type := 'NUMERIC';
|
||||||
|
ELSIF array_length(v_parts,1) = 2 AND v_parts[1] LIKE 'fact_q%' THEN
|
||||||
|
IF v_parts[2] = 'total' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF;
|
||||||
|
IF (v_parts[1]='fact_q1' AND v_parts[2] NOT IN ('jan','feb','mar')) OR
|
||||||
|
(v_parts[1]='fact_q2' AND v_parts[2] NOT IN ('apr','may','jun')) OR
|
||||||
|
(v_parts[1]='fact_q3' AND v_parts[2] NOT IN ('jul','aug','sep')) OR
|
||||||
|
(v_parts[1]='fact_q4' AND v_parts[2] NOT IN ('oct','nov','dec')) THEN
|
||||||
|
RAISE EXCEPTION 'unknown_column: % (месяц вне квартала)', p_column;
|
||||||
|
END IF;
|
||||||
|
v_target_col := 'actual_' || v_parts[2];
|
||||||
|
v_target_type := 'NUMERIC';
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN
|
||||||
|
p_value := '"0"'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE format(
|
||||||
|
'UPDATE v3.%I SET %I = ($2 #>> ''{}'')::%s WHERE id = $1',
|
||||||
|
v_sat_table, v_target_col, v_target_type
|
||||||
|
) USING p_line_id, p_value;
|
||||||
|
|
||||||
|
GET DIAGNOSTICS v_cnt = ROW_COUNT;
|
||||||
|
IF v_cnt = 0 THEN
|
||||||
|
RAISE EXCEPTION '%.id=% не существует', v_sat_table, p_line_id;
|
||||||
|
END IF;
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF array_length(v_parts, 1) < 2 THEN
|
||||||
|
RAISE EXCEPTION 'bad_column_format: %, expected scope.field', p_column;
|
||||||
|
END IF;
|
||||||
|
v_scope := v_parts[1];
|
||||||
|
v_field := v_parts[2];
|
||||||
|
|
||||||
|
IF v_scope = 'header' THEN
|
||||||
|
IF v_field = 'name' THEN
|
||||||
|
SELECT form_type_code INTO v_form_type from v3.budget_form where id = p_form_id;
|
||||||
|
IF v_form_type = 'FORM_4' THEN
|
||||||
|
RAISE EXCEPTION 'Нельзя редактировать поле name';
|
||||||
|
END IF;
|
||||||
|
UPDATE v3.budget_line SET name = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id;
|
||||||
|
ELSIF v_field = 'internal_order' THEN
|
||||||
|
UPDATE v3.budget_line SET internal_order = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id;
|
||||||
|
ELSIF v_field = 'justification' THEN
|
||||||
|
UPDATE v3.budget_line SET justification = (p_value #>> '{}')::TEXT, updated_at = now() WHERE id = p_line_id;
|
||||||
|
ELSIF v_field = 'vsp_id' THEN
|
||||||
|
UPDATE v3.budget_line SET vsp_id = (p_value #>> '{}')::INT, updated_at = now() WHERE id = p_line_id;
|
||||||
|
ELSIF v_field IN ('year','section','item_id','num_group','vsp_address') THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: %', p_column;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_scope IN ('approved','totals') THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_scope = 'booking' THEN
|
||||||
|
IF array_length(v_parts, 1) <> 3 THEN
|
||||||
|
RAISE EXCEPTION 'bad_column_format: %, expected booking.y{2026|2027}.qN', p_column;
|
||||||
|
END IF;
|
||||||
|
IF v_parts[2] NOT IN ('y2026','y2027') THEN
|
||||||
|
RAISE EXCEPTION 'bad_booking_year: % (only y2026/y2027)', v_parts[2];
|
||||||
|
END IF;
|
||||||
|
IF v_parts[3] NOT IN ('q1','q2','q3','q4') THEN
|
||||||
|
RAISE EXCEPTION 'bad_booking_quarter: %', v_parts[3];
|
||||||
|
END IF;
|
||||||
|
v_target_col := CASE v_parts[2]
|
||||||
|
WHEN 'y2026' THEN 'expenses_' || v_parts[3]
|
||||||
|
WHEN 'y2027' THEN 'expenses_next_year_' || v_parts[3]
|
||||||
|
END;
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.ckk (line_id, %1$I) VALUES ($1, ($2 #>> ''{}'')::NUMERIC) '
|
||||||
|
|| 'ON CONFLICT (line_id) DO UPDATE SET %1$I = EXCLUDED.%1$I',
|
||||||
|
v_target_col
|
||||||
|
);
|
||||||
|
EXECUTE v_sql USING p_line_id, p_value;
|
||||||
|
UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id;
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
IF v_scope IN ('q1','q2','q3','q4') THEN
|
||||||
|
v_q := substring(v_scope FROM 2)::SMALLINT;
|
||||||
|
ELSE
|
||||||
|
v_q := NULL;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_actor := CASE v_scope
|
||||||
|
WHEN 'sequestration' THEN 'DFIP'
|
||||||
|
WHEN 'seq_dfip' THEN 'DFIP'
|
||||||
|
WHEN 'seq_ssp' THEN 'SSP_GO'
|
||||||
|
ELSE NULL END;
|
||||||
|
|
||||||
|
SELECT m.target_table, m.target_col, m.target_type, m.key_kind
|
||||||
|
INTO v_target_table, v_target_col, v_target_type, v_key_kind
|
||||||
|
FROM (VALUES
|
||||||
|
('plan','q1', 'plan','plan_q1','NUMERIC','line'),
|
||||||
|
('plan','q2', 'plan','plan_q2','NUMERIC','line'),
|
||||||
|
('plan','q3', 'plan','plan_q3','NUMERIC','line'),
|
||||||
|
('plan','q4', 'plan','plan_q4','NUMERIC','line'),
|
||||||
|
('plan','comment', 'plan','comment','TEXT', 'line'),
|
||||||
|
|
||||||
|
('reserve','q1', 'reserve','amount_q1','NUMERIC','line'),
|
||||||
|
('reserve','q2', 'reserve','amount_q2','NUMERIC','line'),
|
||||||
|
('reserve','q3', 'reserve','amount_q3','NUMERIC','line'),
|
||||||
|
('reserve','q4', 'reserve','amount_q4','NUMERIC','line'),
|
||||||
|
('reserve','justification','reserve','justification','TEXT','line'),
|
||||||
|
|
||||||
|
('allocation','order', 'allocation','internal_order','TEXT','line'),
|
||||||
|
('allocation','property', 'allocation','property_object','TEXT','line'),
|
||||||
|
('allocation','contract_ref', 'allocation','contract_ref','TEXT','line'),
|
||||||
|
('allocation','allocation_purpose', 'allocation','allocation_purpose','TEXT','line'),
|
||||||
|
|
||||||
|
('contract_summary','total', 'contract_summary','total_amount', 'NUMERIC','line'),
|
||||||
|
('contract_summary','counterparty', 'contract_summary','counterparty', 'TEXT', 'line'),
|
||||||
|
('contract_summary','deadline', 'contract_summary','deadline', 'TEXT', 'line'),
|
||||||
|
('contract_summary','comment', 'contract_summary','comment', 'TEXT', 'line'),
|
||||||
|
('contract_summary','future_y1', 'contract_summary','future_payments_y1', 'NUMERIC','line'),
|
||||||
|
('contract_summary','future_y2', 'contract_summary','future_payments_y2', 'NUMERIC','line'),
|
||||||
|
('contract_summary','other_ssp', 'contract_summary','other_ssp_amount', 'NUMERIC','line'),
|
||||||
|
('contract_summary','reference', 'contract_summary','reference', 'TEXT','line'),
|
||||||
|
('contract_summary','centralized_flag', 'contract_summary','centralized_flag', 'TEXT','line'),
|
||||||
|
|
||||||
|
('collegial','approved', 'collegial_approval','approved_amount', 'NUMERIC','line'),
|
||||||
|
('collegial','protocol', 'collegial_approval','protocol_reference','TEXT', 'line'),
|
||||||
|
('collegial','note', 'collegial_approval','note', 'TEXT', 'line'),
|
||||||
|
|
||||||
|
('ckk','ceiling', 'ckk','ceiling_amount', 'NUMERIC','line'),
|
||||||
|
('ckk','q1', 'ckk','expenses_q1', 'NUMERIC','line'),
|
||||||
|
('ckk','q2', 'ckk','expenses_q2', 'NUMERIC','line'),
|
||||||
|
('ckk','q3', 'ckk','expenses_q3', 'NUMERIC','line'),
|
||||||
|
('ckk','q4', 'ckk','expenses_q4', 'NUMERIC','line'),
|
||||||
|
('ckk','next_q1', 'ckk','expenses_next_year_q1', 'NUMERIC','line'),
|
||||||
|
('ckk','next_q2', 'ckk','expenses_next_year_q2', 'NUMERIC','line'),
|
||||||
|
('ckk','next_q3', 'ckk','expenses_next_year_q3', 'NUMERIC','line'),
|
||||||
|
('ckk','next_q4', 'ckk','expenses_next_year_q4', 'NUMERIC','line'),
|
||||||
|
('ckk','rf_schedule', 'ckk','rf_schedule', 'TEXT', 'line'),
|
||||||
|
('ckk','deadline', 'ckk','delivery_deadline', 'TEXT', 'line'),
|
||||||
|
('ckk','proc_plan', 'ckk','procurement_plan', 'TEXT', 'line'),
|
||||||
|
('ckk','proc_method', 'ckk','procurement_method','TEXT', 'line'),
|
||||||
|
('ckk','comment', 'ckk','comment', 'TEXT', 'line'),
|
||||||
|
|
||||||
|
('contract_detail','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'),
|
||||||
|
('contract_detail','reference', 'contract_detail','reference', 'TEXT', 'line'),
|
||||||
|
('contract_detail','addenda', 'contract_detail','addenda', 'TEXT', 'line'),
|
||||||
|
('contract_detail','subject', 'contract_detail','subject', 'TEXT', 'line'),
|
||||||
|
('contract_detail','currency', 'contract_detail','currency', 'TEXT', 'line'),
|
||||||
|
('contract_detail','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'),
|
||||||
|
('contract_detail','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'),
|
||||||
|
('contract_detail','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'),
|
||||||
|
('contract_detail','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'),
|
||||||
|
('contract_detail','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'),
|
||||||
|
('contract_detail','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'),
|
||||||
|
('contract_detail','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'),
|
||||||
|
('contract_detail','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','line'),
|
||||||
|
('contract_detail','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'),
|
||||||
|
('contract_detail','deadline', 'contract_detail','deadline', 'TEXT', 'line'),
|
||||||
|
('contract_detail','payment_scheme', 'contract_detail','payment_scheme','TEXT', 'line'),
|
||||||
|
('contract_detail','act', 'contract_detail','act', 'TEXT', 'line'),
|
||||||
|
('contract_detail','comment', 'contract_detail','comment', 'TEXT', 'line'),
|
||||||
|
('contract_detail','contract_date', 'contract_detail','contract_date', 'DATE', 'line'),
|
||||||
|
|
||||||
|
('contract','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'),
|
||||||
|
('contract','addenda', 'contract_detail','addenda', 'TEXT', 'line'),
|
||||||
|
('contract','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'),
|
||||||
|
('contract','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'),
|
||||||
|
('contract','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'),
|
||||||
|
('contract','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'),
|
||||||
|
('contract','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'),
|
||||||
|
('contract','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'),
|
||||||
|
('contract','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','line'),
|
||||||
|
('contract','reference', 'contract_detail','reference', 'TEXT', 'line'),
|
||||||
|
('contract','date', 'contract_detail','contract_date', 'DATE', 'line'),
|
||||||
|
('contract','subject', 'contract_detail','subject', 'TEXT', 'line'),
|
||||||
|
('contract','currency', 'contract_detail','currency', 'TEXT', 'line'),
|
||||||
|
('contract','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'),
|
||||||
|
('contract','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'),
|
||||||
|
('contract','deadline', 'contract_detail','deadline', 'TEXT', 'line'),
|
||||||
|
('contract','scheme', 'contract_detail','payment_scheme','TEXT', 'line'),
|
||||||
|
('contract','act', 'contract_detail','act', 'TEXT', 'line'),
|
||||||
|
('contract','comment', 'contract_detail','comment', 'TEXT', 'line'),
|
||||||
|
|
||||||
|
('sequestration','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'),
|
||||||
|
('sequestration','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'),
|
||||||
|
('sequestration','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'),
|
||||||
|
('sequestration','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'),
|
||||||
|
('sequestration','justification','sequestration','justification','TEXT', 'line_actor'),
|
||||||
|
|
||||||
|
('seq_dfip','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'),
|
||||||
|
('seq_dfip','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'),
|
||||||
|
('seq_dfip','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'),
|
||||||
|
('seq_dfip','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'),
|
||||||
|
('seq_dfip','justification','sequestration','justification','TEXT', 'line_actor'),
|
||||||
|
|
||||||
|
('seq_ssp','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'),
|
||||||
|
('seq_ssp','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'),
|
||||||
|
('seq_ssp','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'),
|
||||||
|
('seq_ssp','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'),
|
||||||
|
('seq_ssp','justification','sequestration','justification','TEXT', 'line_actor'),
|
||||||
|
|
||||||
|
('q','adj_current', 'budget_line_quarter','adj_current', 'NUMERIC','line_quarter'),
|
||||||
|
('q','adj_ssp', 'budget_line_quarter','adj_ssp', 'NUMERIC','line_quarter'),
|
||||||
|
('q','adj_rf', 'budget_line_quarter','adj_rf', 'NUMERIC','line_quarter'),
|
||||||
|
('q','adj_reserve', 'budget_line_quarter','adj_reserve', 'NUMERIC','line_quarter'),
|
||||||
|
('q','adj_comment', 'budget_line_quarter','adj_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','target_change', 'budget_line_quarter','target_change', 'NUMERIC','line_quarter'),
|
||||||
|
('q','base_correction', 'budget_line_quarter','base_plan_correction', 'NUMERIC','line_quarter'),
|
||||||
|
('q','base_correction_comment', 'budget_line_quarter','base_plan_correction_comment','TEXT', 'line_quarter'),
|
||||||
|
('q','pay_date', 'budget_line_quarter','payment_date', 'DATE', 'line_quarter'),
|
||||||
|
('q','pay_amount', 'budget_line_quarter','payment_amount', 'NUMERIC','line_quarter'),
|
||||||
|
('q','pay_ho', 'budget_line_quarter','payment_amount_ho', 'NUMERIC','line_quarter'),
|
||||||
|
('q','pay_rf', 'budget_line_quarter','payment_amount_rf', 'NUMERIC','line_quarter'),
|
||||||
|
('q','pay_comment', 'budget_line_quarter','payment_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','pay_act', 'budget_line_quarter','payment_act', 'TEXT', 'line_quarter'),
|
||||||
|
('q','actual_m1', 'budget_line_quarter','actual_m1', 'NUMERIC','line_quarter'),
|
||||||
|
('q','actual_m2', 'budget_line_quarter','actual_m2', 'NUMERIC','line_quarter'),
|
||||||
|
('q','actual_m3', 'budget_line_quarter','actual_m3', 'NUMERIC','line_quarter'),
|
||||||
|
('q','actual_spod', 'budget_line_quarter','actual_spod', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q2', 'budget_line_quarter','transfer_to_q2', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q3', 'budget_line_quarter','transfer_to_q3', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q4', 'budget_line_quarter','transfer_to_q4', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_econ', 'budget_line_quarter','transfer_to_economy', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q2_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q2_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q2_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q3_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q3_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q3_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q4_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q4_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_q4_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'),
|
||||||
|
('q','transfer_next_comment', 'budget_line_quarter','transfer_next_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','transfer_far_comment', 'budget_line_quarter','transfer_far_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','rev_eco', 'budget_line_quarter','plan_revision_eco_change','NUMERIC','line_quarter'),
|
||||||
|
('q','rev_item', 'budget_line_quarter','plan_revision_item_adj', 'NUMERIC','line_quarter'),
|
||||||
|
('q','rev_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'),
|
||||||
|
('q','rev_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'),
|
||||||
|
('q','rev_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','revision_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'),
|
||||||
|
('q','revision_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'),
|
||||||
|
('q','revision_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'),
|
||||||
|
('q','booking_amount', 'budget_line_quarter','booking_amount', 'NUMERIC','line_quarter')
|
||||||
|
) AS m(scope, field, target_table, target_col, target_type, key_kind)
|
||||||
|
WHERE m.scope = (CASE WHEN v_q IS NOT NULL THEN 'q' ELSE v_scope END)
|
||||||
|
AND m.field = v_field;
|
||||||
|
|
||||||
|
IF v_target_table IS NULL THEN
|
||||||
|
IF v_field IN ('corrected_plan','new_plan','actual_quarter','booking',
|
||||||
|
'rem_booking','rem_actual','residual_after_booking',
|
||||||
|
'residual_after_actual','economy','total','year') THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: %', p_column;
|
||||||
|
END IF;
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN
|
||||||
|
p_value := '"0"'::jsonb;
|
||||||
|
END IF;
|
||||||
|
IF v_key_kind = 'line' THEN
|
||||||
|
IF v_target_type = 'DATE' THEN
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, to_date($2->>0, ''DD.MM.YYYY'')) '
|
||||||
|
|| 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col
|
||||||
|
);
|
||||||
|
ELSE
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, ($2 #>> ''{}'')::%3$s) '
|
||||||
|
|| 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col, v_target_type
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
EXECUTE v_sql USING p_line_id, p_value;
|
||||||
|
|
||||||
|
ELSIF v_key_kind = 'line_quarter' THEN
|
||||||
|
IF v_q IS NULL THEN RAISE EXCEPTION 'internal: quarter not set for %', p_column; END IF;
|
||||||
|
|
||||||
|
IF v_target_type = 'DATE' THEN
|
||||||
|
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) '
|
||||||
|
|| 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col
|
||||||
|
);
|
||||||
|
|
||||||
|
ELSE
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) '
|
||||||
|
|| 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col, v_target_type
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
EXECUTE v_sql USING p_line_id, v_q, p_value;
|
||||||
|
|
||||||
|
ELSIF v_key_kind = 'line_actor' THEN
|
||||||
|
IF v_actor IS NULL THEN RAISE EXCEPTION 'internal: actor not set for %', p_column; END IF;
|
||||||
|
|
||||||
|
IF v_target_type = 'DATE' THEN
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) '
|
||||||
|
|| 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col, v_target_type
|
||||||
|
);
|
||||||
|
ELSE
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) '
|
||||||
|
|| 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I',
|
||||||
|
v_target_table, v_target_col, v_target_type
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE v_sql USING p_line_id, v_actor, p_value;
|
||||||
|
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'internal: unknown key_kind %', v_key_kind;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
;
|
||||||
|
|
||||||
|
-- DROP FUNCTION v3._apply_form3_cell(int4, text, jsonb);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION v3._apply_form3_cell(p_line_id integer, p_column text, p_value jsonb)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
v_parts TEXT[];
|
||||||
|
v_scope TEXT;
|
||||||
|
v_field TEXT;
|
||||||
|
v_q SMALLINT;
|
||||||
|
v_target_col TEXT;
|
||||||
|
v_target_type TEXT;
|
||||||
|
v_sql TEXT;
|
||||||
|
BEGIN
|
||||||
|
v_parts := string_to_array(p_column, '.');
|
||||||
|
IF array_length(v_parts, 1) < 2 THEN
|
||||||
|
RAISE EXCEPTION 'bad_column_format: %, expected scope.field', p_column;
|
||||||
|
END IF;
|
||||||
|
v_scope := v_parts[1];
|
||||||
|
v_field := v_parts[2];
|
||||||
|
|
||||||
|
IF v_scope = 'totals' OR v_field IN ('total_corr','quarter_actual','economy') THEN
|
||||||
|
RAISE EXCEPTION 'computed_field: %', p_column;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_scope NOT IN ('q1','q2','q3','q4') THEN
|
||||||
|
RAISE EXCEPTION 'unknown_scope: % (FORM_3 supports only q1..q4)', v_scope;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_q := substring(v_scope FROM 2)::SMALLINT;
|
||||||
|
|
||||||
|
CASE v_field
|
||||||
|
WHEN 'adj_by_items' THEN v_target_col := 'adj_by_items'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'carryover' THEN v_target_col := 'carryover'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'adj_increase' THEN v_target_col := 'adj_increase'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'm1' THEN v_target_col := 'actual_m1'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'm2' THEN v_target_col := 'actual_m2'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'm3' THEN v_target_col := 'actual_m3'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'spod' THEN
|
||||||
|
IF v_q <> 4 THEN RAISE EXCEPTION 'spod is q4-only: %', p_column; END IF;
|
||||||
|
v_target_col := 'actual_spod'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'base_plan' THEN v_target_col := 'base_plan'; v_target_type := 'NUMERIC';
|
||||||
|
WHEN 'corrected_plan' THEN
|
||||||
|
IF v_q = 1 THEN RAISE EXCEPTION 'corrected_plan is q2-q4 only: %', p_column; END IF;
|
||||||
|
v_target_col := 'corrected_plan'; v_target_type := 'NUMERIC';
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'unknown_column: %', p_column;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN
|
||||||
|
p_value = '"0"'::jsonb;
|
||||||
|
END IF;
|
||||||
|
v_sql := format(
|
||||||
|
'INSERT INTO v3.rf_project_report_quarter (rf_project_report_line_id, quarter, %1$I) '
|
||||||
|
|| 'VALUES ($1, $2, ($3 #>> ''{}'')::%2$s) '
|
||||||
|
|| 'ON CONFLICT (rf_project_report_line_id, quarter) DO UPDATE SET %1$I = EXCLUDED.%1$I',
|
||||||
|
v_target_col, v_target_type
|
||||||
|
);
|
||||||
|
EXECUTE v_sql USING p_line_id, v_q, p_value;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
;
|
||||||
@ -91,6 +91,7 @@ def test_form_phases_create(client, admin_tokens):
|
|||||||
"column_keys": ["plan.q1"],
|
"column_keys": ["plan.q1"],
|
||||||
"opens_at": "2026-01-01T00:00:00",
|
"opens_at": "2026-01-01T00:00:00",
|
||||||
"closes_at": "2026-12-31T00:00:00",
|
"closes_at": "2026-12-31T00:00:00",
|
||||||
|
"direction": None,
|
||||||
}
|
}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/api/v1/stages/form/4",
|
"/api/v1/stages/form/4",
|
||||||
@ -124,6 +125,7 @@ def test_form_phases_update(client, admin_tokens):
|
|||||||
"column_keys": ["plan.q1"],
|
"column_keys": ["plan.q1"],
|
||||||
"opens_at": "2026-01-01T00:00:00",
|
"opens_at": "2026-01-01T00:00:00",
|
||||||
"closes_at": "2026-12-31T00:00:00",
|
"closes_at": "2026-12-31T00:00:00",
|
||||||
|
"direction": None,
|
||||||
}
|
}
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
"/api/v1/stages/form/4/AHR/test",
|
"/api/v1/stages/form/4/AHR/test",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user