Consolidated commit message to v22 signals
This commit is contained in:
1 parent
6403d25d3e
commit
df289b20b8
443 files changed
+13698
-14972
No files matched your search
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"postgres-db": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-postgres",
|
||||||
|
"${POSTGRES_URL}"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"POSTGRES_URL": "${env:SQLALCHEMY_DATABASE_URI}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filesystem": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-filesystem",
|
||||||
|
"."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angular-cli": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@angular/cli",
|
||||||
|
"mcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Project Architecture & Guidelines
|
||||||
|
|
||||||
|
## Monorepo Layout
|
||||||
|
- **Backend**: `./brewman` (Python, managed via `uv`)
|
||||||
|
- **Frontend**: `./overlord` (Angular, managed via `npm`/`ng`)
|
||||||
|
- **Environment**: Root `.env` file containing shared/database variables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Rules & Commands
|
||||||
|
|
||||||
|
### Backend (`/brewman`)
|
||||||
|
- **Working Directory**: Always execute backend commands within `./brewman` OR pass `--directory brewman` to `uv`.
|
||||||
|
- **Package & Script Management**:
|
||||||
|
- Use `uv` exclusively. Never run bare `pip` or global `python`.
|
||||||
|
- Run scripts/tests: `uv run python <script>` or `uv run pytest` from inside `./brewman`.
|
||||||
|
- Manage dependencies: `uv add <package>` or `uv remove <package>`.
|
||||||
|
- **Environment**: The root `.env` file (`../.env` relative to `brewman`) contains the database connection string and secret keys.
|
||||||
|
|
||||||
|
### Frontend (`/overlord`)
|
||||||
|
- **Working Directory**: Always execute frontend commands within `./overlord`.
|
||||||
|
- **Commands**:
|
||||||
|
- Run dev server: `npm start` or `ng serve`
|
||||||
|
- Code generation: `ng g c components/<name>` (prefer standalone components)
|
||||||
|
- Testing: `ng test --watch=false`
|
||||||
|
- Build: `ng build`
|
||||||
|
|
||||||
|
### Database (PostgreSQL)
|
||||||
|
- Database settings are defined in the root `.env`.
|
||||||
|
- Inspect schema, run migrations, or check types strictly via database tools or `uv run` scripts inside `./brewman`.
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
trigger: glob
|
||||||
|
globs: ./bookie/**/**
|
||||||
|
---
|
||||||
|
|
||||||
|
# Persona
|
||||||
|
|
||||||
|
You are a dedicated Angular developer who thrives on leveraging the absolute latest features of the framework to build cutting-edge applications. You are currently immersed in Angular v20+, passionately adopting signals for reactive state management, embracing standalone components for streamlined architecture, and utilizing the new control flow for more intuitive template logic. Performance is paramount to you, who constantly seeks to optimize change detection and improve user experience through these modern Angular paradigms. When prompted, assume You are familiar with all the newest APIs and best practices, valuing clean, efficient, and maintainable code.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
These are modern examples of how to write an Angular 20 component with signals
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: '{{tag-name}}-root',
|
||||||
|
templateUrl: '{{tag-name}}.html',
|
||||||
|
})
|
||||||
|
export class {{ClassName}} {
|
||||||
|
protected readonly isServerRunning = signal(true);
|
||||||
|
toggleServerStatus() {
|
||||||
|
this.isServerRunning.update(isServerRunning => !isServerRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section class="container">
|
||||||
|
@if (isServerRunning()) {
|
||||||
|
<span>Yes, the server is running</span>
|
||||||
|
} @else {
|
||||||
|
<span>No, the server is not running</span>
|
||||||
|
}
|
||||||
|
<button (click)="toggleServerStatus()">Toggle Server Status</button>
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
When you update a component, be sure to put the logic in the ts file, the styles in the css file and the html template in the html file.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
Here are the some links to the essentials for building Angular applications. Use these to get an understanding of how some of the core functionality works
|
||||||
|
https://angular.dev/essentials/components
|
||||||
|
https://angular.dev/essentials/signals
|
||||||
|
https://angular.dev/essentials/templates
|
||||||
|
https://angular.dev/essentials/dependency-injection
|
||||||
|
|
||||||
|
## Best practices & Style guide
|
||||||
|
|
||||||
|
Here are the best practices and the style guide information.
|
||||||
|
|
||||||
|
### Coding Style guide
|
||||||
|
|
||||||
|
Here is a link to the most recent Angular style guide https://angular.dev/style-guide
|
||||||
|
|
||||||
|
### TypeScript Best Practices
|
||||||
|
|
||||||
|
- Use strict type checking
|
||||||
|
- Prefer type inference when the type is obvious
|
||||||
|
- Avoid the `any` type; use `unknown` when type is uncertain
|
||||||
|
|
||||||
|
### Angular Best Practices
|
||||||
|
|
||||||
|
- Always use standalone components over `NgModules`
|
||||||
|
- Do NOT set `standalone: true` inside the `@Component`, `@Directive` and `@Pipe` decorators
|
||||||
|
- Use signals for state management
|
||||||
|
- Implement lazy loading for feature routes
|
||||||
|
- Use `NgOptimizedImage` for all static images.
|
||||||
|
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
- Keep components small and focused on a single responsibility
|
||||||
|
- Use `input()` signal instead of decorators, learn more here https://angular.dev/guide/components/inputs
|
||||||
|
- Use `output()` function instead of decorators, learn more here https://angular.dev/guide/components/outputs
|
||||||
|
- Use `computed()` for derived state learn more about signals here https://angular.dev/guide/signals.
|
||||||
|
- Prefer inline templates for small components
|
||||||
|
- Prefer Reactive forms instead of Template-driven ones
|
||||||
|
- Do NOT use `ngClass`, use `class` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings
|
||||||
|
- Do NOT use `ngStyle`, use `style` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
- Use signals for local component state
|
||||||
|
- Use `computed()` for derived state
|
||||||
|
- Keep state transformations pure and predictable
|
||||||
|
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
|
||||||
|
- Keep templates simple and avoid complex logic
|
||||||
|
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
||||||
|
- Use the async pipe to handle observables
|
||||||
|
- Use built in pipes and import pipes when being used in a template, learn more https://angular.dev/guide/templates/pipes#
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
- Design services around a single responsibility
|
||||||
|
- Use the `providedIn: 'root'` option for singleton services
|
||||||
|
- Use the `inject()` function instead of constructor injection
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: angular-signals
|
||||||
|
description: Writes modern Angular code using Standalone Components and Signals. Use whenever generating frontend UI or state logic.
|
||||||
|
---
|
||||||
|
# Modern Angular Standards
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
- NEVER generate or use `NgModule`.
|
||||||
|
- ALL components, directives, and pipes must be `standalone: true`.
|
||||||
|
|
||||||
|
## State Management (Signals)
|
||||||
|
- Prefer Angular Signals (`signal()`, `computed()`, `effect()`) for synchronous, local component state instead of `BehaviorSubject`.
|
||||||
|
- Use the new Signal-based inputs and outputs: `input()` and `output()` instead of `@Input()` and `@Output()`.
|
||||||
|
- Use standard RxJS strictly for asynchronous streams (e.g., HTTP requests, router events).
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
- Use the new built-in control flow (`@if`, `@for`, `@switch`) instead of structural directives (`*ngIf`, `*ngFor`).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: fastapi-pydantic-v2
|
||||||
|
description: Writes modern FastAPI endpoints using Pydantic v2 syntax and Annotated dependencies. Use whenever generating or refactoring API routes.
|
||||||
|
---
|
||||||
|
# FastAPI & Pydantic v2 Standards
|
||||||
|
|
||||||
|
## Pydantic v2 Rules
|
||||||
|
- NEVER use `.dict()` or `.json()`. ALWAYS use `.model_dump()` and `.model_dump_json()`.
|
||||||
|
- NEVER use `@validator` or `@root_validator`. ALWAYS use `@field_validator` and `@model_validator(mode="before|after")`.
|
||||||
|
- NEVER use `schema_extra`. Use `json_schema_extra` inside the `model_config` dict.
|
||||||
|
- Use `pydantic_settings.BaseSettings` for all environment variable configurations.
|
||||||
|
|
||||||
|
## FastAPI Rules
|
||||||
|
- ALWAYS use `typing.Annotated` for dependency injection (e.g., `db: Annotated[AsyncSession, Depends(get_db)]`).
|
||||||
|
- Do not use `def` for endpoints that perform I/O; always use `async def`.
|
||||||
|
- Raise `fastapi.HTTPException` for expected client errors instead of returning standard dictionaries.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"kind": "tool-skill",
|
||||||
|
"version": "0.0.19"
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: library-skills
|
||||||
|
description: Use Library Skills to discover, install, refresh, repair, check, and manage agent skills from installed packages.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Library Skills
|
||||||
|
|
||||||
|
Use this skill when a project might benefit from agent skills bundled by its installed packages, or when existing Library Skills-managed symlinks are stale, broken, orphaned, or need to be checked.
|
||||||
|
|
||||||
|
Run commands from the project root.
|
||||||
|
|
||||||
|
Agents bundle their own skills by including an `.agents/skills` directory. More details in [Library Skills](https://library-skills.io).
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
- Make sure project dependencies are installed first, for example with `uv sync` for Python projects or `npm install` / `bun install` for Node.js projects.
|
||||||
|
- Run `uvx library-skills` or `npx library-skills` to discover skills bundled by the installed packages and install selected skills interactively.
|
||||||
|
- Use `uvx library-skills --all` or `npx library-skills --all` only when all newly discovered skills should be installed without selecting individual skills.
|
||||||
|
- Use `uvx library-skills --tool-skill` or `npx library-skills --tool-skill` to copy this Library Skills tool skill into the project so future agents know how to discover, install, update, repair, and check skills.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- Run `uvx library-skills` or `npx library-skills` to discover package-provided skills, install selected new skills, and reconcile existing managed symlinks.
|
||||||
|
- Run `uvx library-skills list` or `npx library-skills list` to inspect discovered and installed skills.
|
||||||
|
- Run `uvx library-skills list --json` or `npx library-skills list --json` for machine-readable installed status.
|
||||||
|
- Run `uvx library-skills scan --json` or `npx library-skills scan --json` for discovery-only automation.
|
||||||
|
- Run `uvx library-skills --check` or `npx library-skills --check` to validate managed skill symlink state without changing files.
|
||||||
|
- Run `uvx library-skills --yes` or `npx library-skills --yes` to repair stale managed symlinks and remove orphaned managed symlinks non-interactively.
|
||||||
|
- Add `--claude` when `.claude/skills` should also be managed.
|
||||||
|
- Add `--skill NAME` to install a specific discovered skill by name.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Prefer rerunning `library-skills` over editing managed symlinks manually.
|
||||||
|
- If installed skill symlinks are broken, dependencies may not be installed yet. Try the project's normal install command first, such as `uv sync`, `npm install`, or `bun install`, then rerun `library-skills`.
|
||||||
|
- Do not delete or overwrite hand-authored skill directories.
|
||||||
|
- Library Skills only removes managed symlinks. It should not remove copied or hand-authored skill directories.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: sqlalchemy-async
|
||||||
|
description: Writes SQLAlchemy 2.0 async database queries using Psycopg3. Use whenever interacting with the PostgreSQL database.
|
||||||
|
---
|
||||||
|
# SQLAlchemy 2.0 Async Standards
|
||||||
|
|
||||||
|
## Model Definitions
|
||||||
|
- ALWAYS use SQLAlchemy 2.0 Declarative Base with `Mapped` and `mapped_column`.
|
||||||
|
- Example: `id: Mapped[int] = mapped_column(primary_key=True)`
|
||||||
|
- NEVER use the old `Column(Integer, primary_key=True)` syntax.
|
||||||
|
|
||||||
|
## Querying
|
||||||
|
- NEVER use `session.query()`. This is legacy 1.x syntax.
|
||||||
|
- ALWAYS use `sqlalchemy.select()` combined with `await session.execute(stmt)`.
|
||||||
|
- Extract results using `.scalars().all()` or `.scalar_one_or_none()`.
|
||||||
|
|
||||||
|
## Session Management
|
||||||
|
- Use `sqlalchemy.ext.asyncio.AsyncSession`.
|
||||||
|
- Ensure all database I/O is awaited.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: strict-mypy
|
||||||
|
description: Enforces strict MyPy type checking conventions matching the project's pyproject.toml configuration. Use whenever writing or modifying Python code.
|
||||||
|
---
|
||||||
|
# Strict MyPy Typing Standards
|
||||||
|
|
||||||
|
This project uses a highly strict MyPy configuration (`strict = true`). When writing or refactoring Python code, you MUST adhere to the following rules to ensure the code passes type checking:
|
||||||
|
|
||||||
|
## 1. Absolute Signature Completeness
|
||||||
|
- EVERY function and method must have fully typed arguments and a return type (`disallow_untyped_defs`, `disallow_incomplete_defs`).
|
||||||
|
- ALWAYS specify `-> None` for functions and methods that do not return a value.
|
||||||
|
- NEVER leave type hints partially defined (e.g., typing only some arguments).
|
||||||
|
- Decorators must be completely type-hinted using `typing.Callable` or `typing.ParamSpec` (`disallow_untyped_decorators`).
|
||||||
|
|
||||||
|
## 2. No Implicit Optionals
|
||||||
|
- `arg: str = None` is strictly forbidden.
|
||||||
|
- You MUST explicitly union with None: `arg: str | None = None` (`no_implicit_optional`).
|
||||||
|
|
||||||
|
## 3. Strict Generics
|
||||||
|
- NEVER use bare collections as types.
|
||||||
|
- ALWAYS specify the inner types: use `list[str]`, `dict[str, int]`, or `tuple[int, ...]` instead of bare `list`, `dict`, or `tuple` (`disallow_any_generics`).
|
||||||
|
|
||||||
|
## 4. Restrictions on `Any` and Subclassing
|
||||||
|
- Do not subclass from untyped external libraries or `Any` (`disallow_subclassing_any`).
|
||||||
|
- Avoid using external types that have no stubs and resolve to `Any` (`disallow_any_unimported`).
|
||||||
|
- Refrain from returning `Any` to avoid contaminating the type inference of calling functions (`warn_return_any`).
|
||||||
|
|
||||||
|
## 5. Pydantic Strictness
|
||||||
|
- `pydantic.mypy` is active with strict initialization.
|
||||||
|
- When instantiating Pydantic models, ensure you are passing exactly the expected typed arguments, as extra or untyped arguments will fail type checks (`init_typed = true`, `init_forbid_extra = true`).
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.15.2
|
rev: v0.16.4
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""recipe steps photos
|
||||||
|
|
||||||
|
Revision ID: 09a4f0ca450f
|
||||||
|
Revises: 82af4dcd0b42
|
||||||
|
Create Date: 2026-03-01 06:22:29.832426
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "09a4f0ca450f"
|
||||||
|
down_revision = "82af4dcd0b42"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.create_table(
|
||||||
|
"recipe_steps",
|
||||||
|
sa.Column("id", sa.Uuid(), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||||
|
sa.Column("recipe_id", sa.Uuid(), nullable=False),
|
||||||
|
sa.Column("order_index", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("phase", sa.Text(), nullable=False),
|
||||||
|
sa.Column("title", sa.Text(), nullable=False),
|
||||||
|
sa.Column("text", sa.Text(), nullable=False),
|
||||||
|
sa.Column("duration_minutes", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(["recipe_id"], ["recipes.id"], ondelete="CASCADE"),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_recipe_steps_recipe_id"), "recipe_steps", ["recipe_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_steps_order_index"), "recipe_steps", ["order_index"], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
"recipe_photos",
|
||||||
|
sa.Column("id", sa.Uuid(), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||||
|
sa.Column("recipe_id", sa.Uuid(), nullable=False),
|
||||||
|
sa.Column("image_id", sa.Uuid(), nullable=False),
|
||||||
|
sa.Column("order_index", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("caption", sa.Text(), nullable=False, server_default=""),
|
||||||
|
sa.ForeignKeyConstraint(["image_id"], ["images.id"], ondelete="CASCADE"),
|
||||||
|
sa.ForeignKeyConstraint(["recipe_id"], ["recipes.id"], ondelete="CASCADE"),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_recipe_photos_recipe_id"), "recipe_photos", ["recipe_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_photos_image_id"), "recipe_photos", ["image_id"], unique=False)
|
||||||
|
op.create_index(op.f("ix_recipe_photos_order_index"), "recipe_photos", ["order_index"], unique=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_index(op.f("ix_recipe_photos_order_index"), table_name="recipe_photos")
|
||||||
|
op.drop_index(op.f("ix_recipe_photos_image_id"), table_name="recipe_photos")
|
||||||
|
op.drop_index(op.f("ix_recipe_photos_recipe_id"), table_name="recipe_photos")
|
||||||
|
op.drop_table("recipe_photos")
|
||||||
|
|
||||||
|
op.drop_index(op.f("ix_recipe_steps_order_index"), table_name="recipe_steps")
|
||||||
|
op.drop_index(op.f("ix_recipe_steps_recipe_id"), table_name="recipe_steps")
|
||||||
|
op.drop_table("recipe_steps")
|
||||||
@@ -17,7 +17,6 @@ from ..models.incentive import Incentive
|
|||||||
from ..models.inventory import Inventory
|
from ..models.inventory import Inventory
|
||||||
from ..models.journal import Journal
|
from ..models.journal import Journal
|
||||||
from ..models.login_history import LoginHistory
|
from ..models.login_history import LoginHistory
|
||||||
from ..models.mozimo_stock_register import MozimoStockRegister
|
|
||||||
from ..models.period import Period
|
from ..models.period import Period
|
||||||
from ..models.permission import Permission
|
from ..models.permission import Permission
|
||||||
from ..models.price import Price
|
from ..models.price import Price
|
||||||
@@ -28,6 +27,8 @@ from ..models.rate_contract import RateContract
|
|||||||
from ..models.rate_contract_item import RateContractItem
|
from ..models.rate_contract_item import RateContractItem
|
||||||
from ..models.recipe import Recipe
|
from ..models.recipe import Recipe
|
||||||
from ..models.recipe_item import RecipeItem
|
from ..models.recipe_item import RecipeItem
|
||||||
|
from ..models.recipe_photo import RecipePhoto
|
||||||
|
from ..models.recipe_step import RecipeStep
|
||||||
from ..models.recipe_tag import RecipeTag
|
from ..models.recipe_tag import RecipeTag
|
||||||
from ..models.recipe_template import RecipeTemplate
|
from ..models.recipe_template import RecipeTemplate
|
||||||
from ..models.role import Role
|
from ..models.role import Role
|
||||||
@@ -62,7 +63,6 @@ __all__ = [
|
|||||||
"Inventory",
|
"Inventory",
|
||||||
"Journal",
|
"Journal",
|
||||||
"LoginHistory",
|
"LoginHistory",
|
||||||
"MozimoStockRegister",
|
|
||||||
"Permission",
|
"Permission",
|
||||||
"Period",
|
"Period",
|
||||||
"Price",
|
"Price",
|
||||||
@@ -73,6 +73,8 @@ __all__ = [
|
|||||||
"RateContractItem",
|
"RateContractItem",
|
||||||
"Recipe",
|
"Recipe",
|
||||||
"RecipeItem",
|
"RecipeItem",
|
||||||
|
"RecipePhoto",
|
||||||
|
"RecipeStep",
|
||||||
"RecipeTag",
|
"RecipeTag",
|
||||||
"RecipeTemplate",
|
"RecipeTemplate",
|
||||||
"reg",
|
"reg",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import secrets
|
||||||
import string
|
import string
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@@ -49,9 +49,9 @@ class Client:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, db: Session) -> Client:
|
def create(cls, db: Session) -> Client:
|
||||||
client_code = random.randint(1000, 9999)
|
client_code = secrets.randbelow(9000) + 1000
|
||||||
otp = random.randint(1000, 9999)
|
otp = secrets.randbelow(9000) + 1000
|
||||||
name = "".join(random.choice(string.ascii_uppercase + string.digits) for x in range(6))
|
name = "".join(secrets.choice(string.ascii_uppercase + string.digits) for x in range(6))
|
||||||
client = Client(client_code, name, False, otp)
|
client = Client(client_code, name, False, otp)
|
||||||
db.add(client)
|
db.add(client)
|
||||||
return client
|
return client
|
||||||
@@ -6,7 +6,7 @@ from datetime import date
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from sqlalchemy import Date, ForeignKey, Numeric, Text, UniqueConstraint, Uuid, text
|
from sqlalchemy import Date, ForeignKey, Numeric, Text, Uuid, text
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from ..db.base_class import reg
|
from ..db.base_class import reg
|
||||||
@@ -15,6 +15,8 @@ from .recipe_tag import RecipeTag
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .recipe_item import RecipeItem
|
from .recipe_item import RecipeItem
|
||||||
|
from .recipe_photo import RecipePhoto
|
||||||
|
from .recipe_step import RecipeStep
|
||||||
from .stock_keeping_unit import StockKeepingUnit
|
from .stock_keeping_unit import StockKeepingUnit
|
||||||
from .tag import Tag
|
from .tag import Tag
|
||||||
|
|
||||||
@@ -22,22 +24,34 @@ if TYPE_CHECKING:
|
|||||||
@reg.mapped_as_dataclass(unsafe_hash=True)
|
@reg.mapped_as_dataclass(unsafe_hash=True)
|
||||||
class Recipe:
|
class Recipe:
|
||||||
__tablename__ = "recipes"
|
__tablename__ = "recipes"
|
||||||
__table_args__ = (UniqueConstraint("sku_id", "date"),)
|
|
||||||
|
|
||||||
id: Mapped[uuid.UUID] = mapped_column(
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
Uuid, primary_key=True, insert_default=uuid.uuid4, server_default=text("gen_random_uuid()")
|
Uuid, primary_key=True, insert_default=uuid.uuid4, server_default=text("gen_random_uuid()")
|
||||||
)
|
)
|
||||||
sku_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("stock_keeping_units.id"), nullable=False)
|
|
||||||
date_: Mapped[date] = mapped_column("date", Date, nullable=False, index=True)
|
date_: Mapped[date] = mapped_column("date", Date(), nullable=False)
|
||||||
source: Mapped[str] = mapped_column(Text, nullable=False)
|
source: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
instructions: Mapped[str] = mapped_column(Text, nullable=False)
|
instructions: Mapped[str] = mapped_column(Text, nullable=False) # legacy single-text instructions
|
||||||
garnishing: Mapped[str] = mapped_column(Text, nullable=False)
|
garnishing: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
plating: Mapped[str] = mapped_column(Text, nullable=False)
|
plating: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
notes: Mapped[str] = mapped_column(Text, nullable=False)
|
notes: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
sku_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("stock_keeping_units.id"), nullable=False)
|
||||||
recipe_yield: Mapped[Decimal] = mapped_column(Numeric(precision=15, scale=2), nullable=False)
|
recipe_yield: Mapped[Decimal] = mapped_column(Numeric(12, 4), nullable=False)
|
||||||
|
|
||||||
items: Mapped[list[RecipeItem]] = relationship("RecipeItem", back_populates="recipe")
|
items: Mapped[list[RecipeItem]] = relationship("RecipeItem", back_populates="recipe")
|
||||||
|
steps: Mapped[list[RecipeStep]] = relationship(
|
||||||
|
"RecipeStep",
|
||||||
|
back_populates="recipe",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
order_by="RecipeStep.order_index",
|
||||||
|
)
|
||||||
|
photos: Mapped[list[RecipePhoto]] = relationship(
|
||||||
|
"RecipePhoto",
|
||||||
|
back_populates="recipe",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
order_by="RecipePhoto.order_index",
|
||||||
|
)
|
||||||
|
|
||||||
sku: Mapped[StockKeepingUnit] = relationship("StockKeepingUnit", back_populates="recipes")
|
sku: Mapped[StockKeepingUnit] = relationship("StockKeepingUnit", back_populates="recipes")
|
||||||
tags: Mapped[list[Tag]] = relationship(
|
tags: Mapped[list[Tag]] = relationship(
|
||||||
"Tag",
|
"Tag",
|
||||||
@@ -54,21 +68,17 @@ class Recipe:
|
|||||||
garnishing: str,
|
garnishing: str,
|
||||||
plating: str,
|
plating: str,
|
||||||
notes: str,
|
notes: str,
|
||||||
|
sku: StockKeepingUnit,
|
||||||
recipe_yield: Decimal,
|
recipe_yield: Decimal,
|
||||||
sku_id: uuid.UUID | None = None,
|
|
||||||
sku: StockKeepingUnit | None = None,
|
|
||||||
id_: uuid.UUID | None = None,
|
id_: uuid.UUID | None = None,
|
||||||
):
|
) -> None:
|
||||||
self.date_ = date_
|
self.date_ = date_
|
||||||
self.source = source
|
self.source = source
|
||||||
self.instructions = instructions
|
self.instructions = instructions
|
||||||
self.garnishing = garnishing
|
self.garnishing = garnishing
|
||||||
self.plating = plating
|
self.plating = plating
|
||||||
self.notes = notes
|
self.notes = notes
|
||||||
self.recipe_yield = recipe_yield
|
|
||||||
if sku_id is not None:
|
|
||||||
self.sku_id = sku_id
|
|
||||||
if sku is not None:
|
|
||||||
self.sku = sku
|
self.sku = sku
|
||||||
|
self.recipe_yield = recipe_yield
|
||||||
if id_ is not None:
|
if id_ is not None:
|
||||||
self.id = id_
|
self.id = id_
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey, Integer, Text, Uuid, text
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from ..db.base_class import reg
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .db_image import DbImage
|
||||||
|
from .recipe import Recipe
|
||||||
|
|
||||||
|
|
||||||
|
@reg.mapped_as_dataclass(unsafe_hash=True)
|
||||||
|
class RecipePhoto:
|
||||||
|
"""Links recipes to DbImage with caption + ordering."""
|
||||||
|
|
||||||
|
__tablename__ = "recipe_photos"
|
||||||
|
|
||||||
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
Uuid, primary_key=True, insert_default=uuid.uuid4, server_default=text("gen_random_uuid()")
|
||||||
|
)
|
||||||
|
recipe_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("recipes.id"), nullable=False, index=True)
|
||||||
|
image_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("images.id"), nullable=False, index=True)
|
||||||
|
|
||||||
|
order_index: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||||
|
caption: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
|
||||||
|
recipe: Mapped[Recipe] = relationship("Recipe", back_populates="photos")
|
||||||
|
image: Mapped[DbImage] = relationship("DbImage")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
recipe_id: uuid.UUID,
|
||||||
|
image_id: uuid.UUID,
|
||||||
|
order_index: int,
|
||||||
|
caption: str = "",
|
||||||
|
id_: uuid.UUID | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.recipe_id = recipe_id
|
||||||
|
self.image_id = image_id
|
||||||
|
self.order_index = order_index
|
||||||
|
self.caption = caption
|
||||||
|
if id_ is not None:
|
||||||
|
self.id = id_
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlalchemy import ForeignKey, Integer, Text, Uuid, text
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
|
from ..db.base_class import reg
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .recipe import Recipe
|
||||||
|
|
||||||
|
|
||||||
|
@reg.mapped_as_dataclass(unsafe_hash=True)
|
||||||
|
class RecipeStep:
|
||||||
|
"""Structured recipe steps.
|
||||||
|
|
||||||
|
Stored separately from legacy Recipe.instructions (single text field).
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "recipe_steps"
|
||||||
|
|
||||||
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
Uuid, primary_key=True, insert_default=uuid.uuid4, server_default=text("gen_random_uuid()")
|
||||||
|
)
|
||||||
|
recipe_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("recipes.id"), nullable=False, index=True)
|
||||||
|
|
||||||
|
order_index: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
|
||||||
|
phase: Mapped[str] = mapped_column(Text, nullable=False) # Prep/Cook/Finish/Serve
|
||||||
|
title: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
|
duration_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||||
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
|
||||||
|
recipe: Mapped[Recipe] = relationship("Recipe", back_populates="steps")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
recipe_id: uuid.UUID,
|
||||||
|
order_index: int,
|
||||||
|
phase: str,
|
||||||
|
title: str,
|
||||||
|
text: str,
|
||||||
|
duration_minutes: int | None = None,
|
||||||
|
notes: str | None = None,
|
||||||
|
id_: uuid.UUID | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.recipe_id = recipe_id
|
||||||
|
self.order_index = order_index
|
||||||
|
self.phase = phase
|
||||||
|
self.title = title
|
||||||
|
self.text = text
|
||||||
|
self.duration_minutes = duration_minutes
|
||||||
|
self.notes = notes
|
||||||
|
if id_ is not None:
|
||||||
|
self.id = id_
|
||||||
+518
-341
File diff suppressed because it is too large.
Load diff
@@ -3,7 +3,7 @@ from datetime import date, datetime, time, timedelta
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Security
|
from fastapi import APIRouter, Depends, Query, Security
|
||||||
from sqlalchemy import desc, not_, or_, select
|
from sqlalchemy import desc, not_, or_, select
|
||||||
from sqlalchemy.orm import Session, contains_eager
|
from sqlalchemy.orm import Session, contains_eager
|
||||||
from sqlalchemy.sql.functions import count
|
from sqlalchemy.sql.functions import count
|
||||||
@@ -88,15 +88,24 @@ def report_data(
|
|||||||
finish_date: Annotated[date, Depends(report_finish_date)],
|
finish_date: Annotated[date, Depends(report_finish_date)],
|
||||||
user: Annotated[UserToken, Security(get_user, scopes=["ledger"])],
|
user: Annotated[UserToken, Security(get_user, scopes=["ledger"])],
|
||||||
db: SessionDep,
|
db: SessionDep,
|
||||||
p: bool | None = None, # Posted
|
posted: Annotated[bool | None, Query(alias="p")] = None, # Posted
|
||||||
ps: int = 50, # Page Size
|
page_size: Annotated[int, Query(alias="ps")] = 50, # Page Size
|
||||||
pi: int = 0, # Page Index
|
page_index: Annotated[int, Query(alias="pi")] = 0, # Page Index
|
||||||
a: str = "date", # Active Sort
|
active_sort: Annotated[str, Query(alias="a")] = "date", # Active Sort
|
||||||
d: str = "desc", # Sort Direction
|
sort_direction: Annotated[str, Query(alias="d")] = "desc", # Sort Direction
|
||||||
i: bool = False, # Show issue vouchers
|
show_issue_vouchers: Annotated[bool, Query(alias="i")] = False, # Show issue vouchers
|
||||||
) -> schemas.Report:
|
) -> schemas.Report:
|
||||||
assert i is not None and ps is not None and pi is not None and a is not None and d is not None
|
counts, report = build_report(
|
||||||
counts, report = build_report(start_date, finish_date, p, i, ps, pi, a, d, db)
|
start_date,
|
||||||
|
finish_date,
|
||||||
|
posted,
|
||||||
|
show_issue_vouchers,
|
||||||
|
page_size,
|
||||||
|
page_index,
|
||||||
|
active_sort,
|
||||||
|
sort_direction,
|
||||||
|
db,
|
||||||
|
)
|
||||||
return schemas.Report(
|
return schemas.Report(
|
||||||
counts=counts,
|
counts=counts,
|
||||||
report=[
|
report=[
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from ..models.product_group import ProductGroup
|
|||||||
from ..models.product_version import ProductVersion
|
from ..models.product_version import ProductVersion
|
||||||
from ..models.sku_version import SkuVersion
|
from ..models.sku_version import SkuVersion
|
||||||
from ..models.stock_keeping_unit import StockKeepingUnit
|
from ..models.stock_keeping_unit import StockKeepingUnit
|
||||||
from ..schemas.account import AccountLink
|
|
||||||
from ..schemas.product_group import ProductGroupLink
|
from ..schemas.product_group import ProductGroupLink
|
||||||
from ..schemas.temporal_product import Product as ProductModel
|
from ..schemas.temporal_product import Product as ProductModel
|
||||||
from ..schemas.temporal_product import StockKeepingUnit as SkuModel
|
from ..schemas.temporal_product import StockKeepingUnit as SkuModel
|
||||||
@@ -52,7 +51,7 @@ def update_route(
|
|||||||
product_v.description = data_version.description
|
product_v.description = data_version.description
|
||||||
product_v.fraction_units = data_version.fraction_units
|
product_v.fraction_units = data_version.fraction_units
|
||||||
product_v.product_group_id = data_version.product_group.id_
|
product_v.product_group_id = data_version.product_group.id_
|
||||||
product_v.account_id = Account.all_purchases()
|
# product_v.account_id = Account.all_purchases()
|
||||||
product_v.is_purchased = data_version.is_purchased
|
product_v.is_purchased = data_version.is_purchased
|
||||||
product_v.is_sold = data_version.is_sold
|
product_v.is_sold = data_version.is_sold
|
||||||
product_v.allergen = data_version.allergen
|
product_v.allergen = data_version.allergen
|
||||||
@@ -225,7 +224,7 @@ def product_info(product: Product) -> TemporalProduct:
|
|||||||
is_purchased=version.is_purchased,
|
is_purchased=version.is_purchased,
|
||||||
is_sold=version.is_sold,
|
is_sold=version.is_sold,
|
||||||
product_group=ProductGroupLink(id_=version.product_group.id, name=version.product_group.name),
|
product_group=ProductGroupLink(id_=version.product_group.id, name=version.product_group.name),
|
||||||
account=AccountLink(id_=version.account.id, name=version.account.name),
|
# account=AccountLink(id_=version.account.id, name=version.account.name),
|
||||||
allergen=version.allergen,
|
allergen=version.allergen,
|
||||||
protein=version.protein,
|
protein=version.protein,
|
||||||
carbohydrate=version.carbohydrate,
|
carbohydrate=version.carbohydrate,
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import uuid
|
|||||||
|
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
from fastapi import Form
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
|
AliasChoices,
|
||||||
BaseModel,
|
BaseModel,
|
||||||
ConfigDict,
|
ConfigDict,
|
||||||
|
Field,
|
||||||
FieldSerializationInfo,
|
FieldSerializationInfo,
|
||||||
field_serializer,
|
field_serializer,
|
||||||
field_validator,
|
field_validator,
|
||||||
@@ -13,24 +16,37 @@ from pydantic import (
|
|||||||
from . import Daf, to_camel
|
from . import Daf, to_camel
|
||||||
from .product import ProductLink
|
from .product import ProductLink
|
||||||
from .recipe_item import RecipeItem
|
from .recipe_item import RecipeItem
|
||||||
|
from .recipe_photo import RecipePhoto
|
||||||
|
from .recipe_step import RecipeStep
|
||||||
|
|
||||||
|
|
||||||
class RecipeIn(BaseModel):
|
class RecipeIn(BaseModel):
|
||||||
sku: ProductLink
|
|
||||||
date_: date
|
date_: date
|
||||||
source: str
|
source: str
|
||||||
instructions: str
|
instructions: str
|
||||||
garnishing: str
|
garnishing: str
|
||||||
plating: str
|
plating: str
|
||||||
|
sku: ProductLink
|
||||||
recipe_yield: Daf
|
recipe_yield: Daf
|
||||||
notes: str
|
notes: str
|
||||||
|
|
||||||
items: list[RecipeItem]
|
# New structured content
|
||||||
|
steps: list[RecipeStep] = Field(default_factory=list)
|
||||||
|
photos: list[RecipePhoto] = Field(default_factory=list)
|
||||||
|
|
||||||
|
# Ingredients (renamed from legacy "items")
|
||||||
|
# Accept both keys for backward compatibility, serialize as "ingredients".
|
||||||
|
ingredients: list[RecipeItem] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
validation_alias=AliasChoices("ingredients", "items"),
|
||||||
|
serialization_alias="ingredients",
|
||||||
|
)
|
||||||
|
|
||||||
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
|
|
||||||
@field_validator("date_", mode="before")
|
@field_validator("date_", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_start_date(cls, value: date | str) -> date:
|
def parse_date(cls, value: date | str) -> date:
|
||||||
if isinstance(value, date):
|
if isinstance(value, date):
|
||||||
return value
|
return value
|
||||||
return datetime.strptime(value, "%d-%b-%Y").date()
|
return datetime.strptime(value, "%d-%b-%Y").date()
|
||||||
@@ -39,11 +55,25 @@ class RecipeIn(BaseModel):
|
|||||||
def serialize_date(self, value: date, info: FieldSerializationInfo) -> str:
|
def serialize_date(self, value: date, info: FieldSerializationInfo) -> str:
|
||||||
return value.strftime("%d-%b-%Y")
|
return value.strftime("%d-%b-%Y")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_form(cls, data: str = Form(...)) -> BaseModel:
|
||||||
|
import json
|
||||||
|
|
||||||
|
json_data = json.loads(data)
|
||||||
|
# pydantic v2
|
||||||
|
return cls.model_validate(json_data)
|
||||||
|
|
||||||
|
|
||||||
class Recipe(RecipeIn):
|
class Recipe(RecipeIn):
|
||||||
id_: uuid.UUID
|
id_: uuid.UUID
|
||||||
product_group_id: uuid.UUID | None = None
|
product_group_id: uuid.UUID | None = None
|
||||||
units: str | None = None
|
units: str | None = None
|
||||||
|
|
||||||
|
# Derived metrics (for list view)
|
||||||
|
step_count: int | None = None
|
||||||
|
photo_count: int | None = None
|
||||||
|
total_duration_minutes: int | None = None
|
||||||
|
|
||||||
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
|
from . import to_camel
|
||||||
|
|
||||||
|
|
||||||
|
class RecipePhotoIn(BaseModel):
|
||||||
|
"""Photo link + metadata.
|
||||||
|
|
||||||
|
For input:
|
||||||
|
- id_ is optional (existing image id)
|
||||||
|
- resized / thumbnail are base64 strings (may include data URL prefix)
|
||||||
|
For output:
|
||||||
|
- resized / thumbnail are URLs to /db-image/{id}/...
|
||||||
|
"""
|
||||||
|
|
||||||
|
id_: uuid.UUID | None = None
|
||||||
|
order_index: int = Field(0, ge=0)
|
||||||
|
caption: str = ""
|
||||||
|
resized: str | None = None
|
||||||
|
thumbnail: str | None = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
|
|
||||||
|
|
||||||
|
class RecipePhoto(RecipePhotoIn):
|
||||||
|
# kept for symmetry / future extensions
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
|
from . import to_camel
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeStepIn(BaseModel):
|
||||||
|
order_index: int = Field(0, ge=0)
|
||||||
|
phase: str
|
||||||
|
title: str
|
||||||
|
text: str
|
||||||
|
duration_minutes: int | None = Field(default=None, ge=0)
|
||||||
|
notes: str | None = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeStep(RecipeStepIn):
|
||||||
|
id_: uuid.UUID | None = None
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True, alias_generator=to_camel, populate_by_name=True)
|
||||||
@@ -7,7 +7,6 @@ from typing import Annotated
|
|||||||
from pydantic import BaseModel, ConfigDict, Field, SerializationInfo, field_serializer, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, SerializationInfo, field_serializer, field_validator
|
||||||
|
|
||||||
from . import Daf, to_camel
|
from . import Daf, to_camel
|
||||||
from .account import AccountLink
|
|
||||||
from .product_group import ProductGroupLink
|
from .product_group import ProductGroupLink
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ class Product(BaseModel):
|
|||||||
description: str | None
|
description: str | None
|
||||||
fraction_units: Annotated[str, Field(min_length=1)]
|
fraction_units: Annotated[str, Field(min_length=1)]
|
||||||
product_group: ProductGroupLink = Field(...)
|
product_group: ProductGroupLink = Field(...)
|
||||||
account: AccountLink
|
|
||||||
is_purchased: bool
|
is_purchased: bool
|
||||||
is_sold: bool
|
is_sold: bool
|
||||||
|
|
||||||
|
|||||||
+19
-20
@@ -5,33 +5,34 @@ description = "Accounting plus inventory management for a restaurant."
|
|||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
authors = [{ name = "tanshu", email = "git@tanshu.com" }]
|
authors = [{ name = "tanshu", email = "git@tanshu.com" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alembic>=1.18.4",
|
"alembic>=1.19.1",
|
||||||
"arq>=0.28.0",
|
"arq>=0.28.0",
|
||||||
"cryptography>=48.0.0",
|
"cryptography>=50.0.0",
|
||||||
"fastapi[all]>=0.136.1",
|
"fastapi[all]>=0.141.1",
|
||||||
"gunicorn>=26.0.0",
|
"gunicorn>=26.2.0",
|
||||||
"itsdangerous>=2.2.0",
|
"itsdangerous>=2.2.0",
|
||||||
"openpyxl>=3.1.5",
|
"openpyxl>=3.1.5",
|
||||||
"pandas>=3.0.2",
|
"pandas>=3.0.5",
|
||||||
"passlib[bcrypt]>=1.7.4",
|
"passlib[bcrypt]>=1.7.4",
|
||||||
"psycopg[binary,pool]>=3.3.4",
|
"psycopg[binary,pool]>=3.3.4",
|
||||||
"pydantic>=2.13.3",
|
"pydantic>=2.13.4",
|
||||||
"pydantic-settings>=2.14.0",
|
"pydantic-settings>=2.15.0",
|
||||||
"pyjwt>=2.12.1",
|
"pyjwt>=2.13.0",
|
||||||
"python-dotenv>=1.2.2",
|
"python-dotenv>=1.2.3",
|
||||||
"python-multipart>=0.0.27",
|
"python-multipart>=0.0.32",
|
||||||
"sqlalchemy>=2.0.49",
|
"sqlalchemy>=2.0.52",
|
||||||
"uvicorn[standard]>=0.46.0",
|
"uvicorn[standard]>=0.52.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"bandit>=1.9.4",
|
"bandit>=1.9.4",
|
||||||
"mypy>=1.20.2",
|
"mypy>=2.3.1",
|
||||||
"pre-commit>=4.6.0",
|
"pandas-stubs>=3.0.5.260730",
|
||||||
"ruff>=0.15.12",
|
"pre-commit>=4.6.2",
|
||||||
"safety>=3.7.0",
|
"ruff>=0.16.4",
|
||||||
"types-openpyxl>=3.1.5.20260408",
|
"safety>=3.8.1",
|
||||||
|
"types-openpyxl>=3.1.5.20260807",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
@@ -107,9 +108,7 @@ ignore = [
|
|||||||
"W191", # indentation contains tabs
|
"W191", # indentation contains tabs
|
||||||
"B904", # Allow raising exceptions without from e, for HTTPException
|
"B904", # Allow raising exceptions without from e, for HTTPException
|
||||||
]
|
]
|
||||||
fixable = [
|
fixable = ["ALL"] # Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
"ALL"
|
|
||||||
] # Allow fix for all enabled rules (when `--fix`) is provided.
|
|
||||||
dummy-variable-rgx = "^(user|_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
dummy-variable-rgx = "^(user|_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
[tool.ruff.lint.isort]
|
||||||
|
|||||||
Generated
+377
-285
@@ -12,16 +12,16 @@ resolution-markers = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alembic"
|
name = "alembic"
|
||||||
version = "1.18.4"
|
version = "1.19.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mako" },
|
{ name = "mako" },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/16/2b/e4153978368de59918115c9e01d3ebf58a558a7285efa7e960c383c4b59a/alembic-1.19.1.tar.gz", hash = "sha256:e0fca0518118c78acc493e31bcb5402f190057aaf6df8b5b95ce94c4789cf648", size = 2070816, upload-time = "2026-08-08T16:32:01.565Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/89/e62cc37b69ad357cc8ecd6e7367f5245f523d3cbb338a66197212bdf6749/alembic-1.19.1-py3-none-any.whl", hash = "sha256:b39018cb3d9413a19cbd54cf3c02ad33998641f0538eb77413a488a21c3e14be", size = 265946, upload-time = "2026-08-08T16:32:03.153Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -67,6 +67,70 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/48/32/66b616976c5058d434ca2017979bfffd784888177b16b5038abcec93954a/arq-0.28.0-py3-none-any.whl", hash = "sha256:b1696bf5614d60f4172a2c0cbdc177e23ba03a5eb9acc29bd8181f4ea71fff94", size = 26061, upload-time = "2026-04-16T10:50:22.321Z" },
|
{ url = "https://files.pythonhosted.org/packages/48/32/66b616976c5058d434ca2017979bfffd784888177b16b5038abcec93954a/arq-0.28.0-py3-none-any.whl", hash = "sha256:b1696bf5614d60f4172a2c0cbdc177e23ba03a5eb9acc29bd8181f4ea71fff94", size = 26061, upload-time = "2026-04-16T10:50:22.321Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ast-serialize"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e1/a9/11851c3e02a3fea2ddc9932d1fdc7d2edaeecc0d2e11bc5f2a7fde2b0934/ast_serialize-0.8.0.tar.gz", hash = "sha256:6c37c43e4004dfb42d321ddedc569dc17ff4259296f3af577c9ea46a809bc010", size = 845638, upload-time = "2026-08-07T11:29:02.152Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/16/6e520b57cd8c75914b38c670ad4593d13c22911e4306cc7165dab8b0789b/ast_serialize-0.8.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3d822605fa7bb326ef868d25fafced7fc660fa46d9b90c02ea86d5e2f5d325f7", size = 863924, upload-time = "2026-08-07T11:27:34.579Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/e1/48802de9b22a2bcad42ec80601a17e3f69172fe4f590e6311bcc2b323aeb/ast_serialize-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:2efa40b068197d5efb62655b43baadb842ed71c4958cccd3e8b86a35726f0119", size = 1177662, upload-time = "2026-08-07T11:27:36.196Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/d4/323438db76bded3a1f3523a3167b8325916b2ddceb2107a330c6ec9fcf4d/ast_serialize-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:db1b957291bca08c7e72f43a12357b2948e20775d970e3fc3dac0aa3160ab725", size = 1167072, upload-time = "2026-08-07T11:27:37.646Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/82/53c5400b54144b56de8ed7f957fd1ccd97e42482009292ab46121d15f8dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdc0d5b18ff8fb364e87923e47c0a91d0d69dbcaeaa274591f7fd26892cc3a3a", size = 1225497, upload-time = "2026-08-07T11:27:39.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/5f/36c07327a8b91303fbf1382c7c3e8a2902072dbe1b9546138a5288e75ff0/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9da7330f3e235bf7da89b8d39205c6350fc0c08a85379743f2df9fff87d6d980", size = 1227101, upload-time = "2026-08-07T11:27:40.799Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/48/5adf5c67addc7ddb328122208c6d375a84cf154984f412b4087330a157bd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3186969ee66a9863b00acc6523ace44c56974eecb348a7ea4b228d9f0b80e19", size = 1424001, upload-time = "2026-08-07T11:27:42.708Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/a1/70074dd3869d2b0e934f91891d8d6b734361cd3b80f85ca7ece2e668ecdd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40a57b73731be45da4fa41430c4d5dc94a24b3a4faba7b9e069978c0402064ea", size = 1245545, upload-time = "2026-08-07T11:27:44.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/be/53b9c0a8a6399950c2e3546bdfab96d2b299d5b114b47eb94fd3c49c4054/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b9da3ef807eda752502446dfecea3b381c4900b7e27a5d5f4f899eb39951", size = 1248961, upload-time = "2026-08-07T11:27:45.781Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/13/3651d3812548a2bda15e26e5dd51aadb48cf682d0865370255fcf0e367dd/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:293cc1c5bfa741f8e3fbe8175b9c07beee487c9a6fdbb25a5acad9f1df2d30a9", size = 1243877, upload-time = "2026-08-07T11:27:47.325Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/a0/521f0bf000f675e9312a4aae2c8ba7a992405d072a85c485e08fd59433b9/ast_serialize-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0910c3442a75216dde0f102d854ba2aaa71d2482e0ee213630b9bf29584fba3", size = 1293903, upload-time = "2026-08-07T11:27:49.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/7e/402fc902568aa2ee65865a3e151f000db0153da8ce6b1be4c9c349025f8d/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43dd6d596879bb1cb8a12cc9dae7bb10090a39a35883026c24f82488a195619a", size = 1401070, upload-time = "2026-08-07T11:27:50.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/7c/97d4b66c057f1706fc8be6dd532cc77c988794357c8f4ffdb6adabb39562/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8c9d537f59e936392cfd3597789d1390304dd659efc3c486ce7f40fb6b8a9f53", size = 1502602, upload-time = "2026-08-07T11:27:52.364Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/6f/72cc3b71562001bba46e898ccfbf1844f7939b3e28912736206102f2e5a8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f0190a33d7f97c65e9069f7a7f40499eea6b5cbe260c558378109caf20ce934b", size = 1495848, upload-time = "2026-08-07T11:27:53.803Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/53/d6f629d1e49308b2f363dae028baa213ec222c9106fa1f7f0d1f7b41499a/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:77308ae6c5cf5264cc0f01a7c556ec77a9e68eb1f61b093534d698139fdc3b14", size = 1556556, upload-time = "2026-08-07T11:27:55.342Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/22/340f35dd8dfc6d412d53dc20699ca014b8d228db923e8ed4759c512b162c/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8d53a23f27e1ed3a36b2d26fd2a1a6228c8e85a1ed62ff7cdb44bd610769f20a", size = 1417822, upload-time = "2026-08-07T11:27:56.712Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/29/6dde5c13fbebc051d3a6df4ec0a6fd1d5359333cc1193f7f609f3410b4d8/ast_serialize-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa5e7cb08f96fed9121f77b224151e41caf88feab9d652bb46c78202b6fbeda", size = 1445153, upload-time = "2026-08-07T11:27:58.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/c5/f473a8ed030f7a0ca24b9849cca184677a50c053867a7b808c2e1289bbd3/ast_serialize-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:fa70ed4dea0bb18b30a1789c77baa701d0ef30c474f2ccabdea61e25623a8827", size = 1063711, upload-time = "2026-08-07T11:27:59.793Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/63/39e171fcd38ca057c2e1979d5ee81ac7a3502784abe3d83df7454f7a0978/ast_serialize-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d8b3c8eee4c1baef9d4e84d2a59a805501617127be42615cb48970b15b0892b6", size = 1103740, upload-time = "2026-08-07T11:28:01.405Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/1c/d00762b399e7726d68d0a088cc946e3a4c60f1c6176f557608f672f627f3/ast_serialize-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:ac4f0a83c55a9b782f79ad55a5247b7db123c1db405959791c2ef886e9710c9f", size = 1076021, upload-time = "2026-08-07T11:28:02.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/11/911210c3c78923273a9211a2b6cfc4c8aa723b30dab3e1c8d19afb983b40/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:86b8a1e6d90467345356098b040150e82fbc26d24a7a202224b13dc1f6264ca0", size = 1177715, upload-time = "2026-08-07T11:28:04.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/89/6282881c8587606638db153cbe21e1e0c4d1f3970dee1aa0610a1c62a026/ast_serialize-0.8.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:39e92ff8e8cb45947fe9007174b2950e1fb098e6abd00266a13cd3bcf6675068", size = 1169347, upload-time = "2026-08-07T11:28:06.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/78/a9f846a03a340ff3728c915f23338ca742742f3292700559cdb3ad999b1e/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c85d8d18db5b2dfcb3b7e38a4d600ca35504c0ed8a6f75cd1c811e4ffe248a15", size = 1225916, upload-time = "2026-08-07T11:28:07.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/15/aba6ef8a988a6eceb6f0359589aac509e29ae2dba67fd9bfd5af0c3f13e7/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9830ff7e764f74d9eefb01170c61a9f0fd2c027dac5fcb72e064decd57d56371", size = 1227135, upload-time = "2026-08-07T11:28:09.504Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/29/3f63d696ea7c5b8abadcecc3505be51bd900daaccc522ed8322fa5b05a93/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6479d9722a4cd21b578f5478074c41e6169f04811996ec881655560f703a5bba", size = 1425040, upload-time = "2026-08-07T11:28:11.044Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/5d/0aac338604ff59df5774d4304307898982252f325ff7cafe31d52fedcb65/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a63bed264e818cd83eec11feed0f50aa162542b91132ef58afebc857182763a5", size = 1246278, upload-time = "2026-08-07T11:28:12.519Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/ca/9f1ef795bb724719532bd86dbec11e5b66857d3fbe9b6772baec0191a6ed/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d187197d234aa45d6cfa2b096be5f666e8cc2e7eb3722d0ab8926293cf5720c", size = 1250029, upload-time = "2026-08-07T11:28:13.896Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/25/5e061372d2ed953b9ba3b9c4f73de3b8e9234cda3f6c088db4686801d0e1/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:2d39a56282cfcc0d8eeea37267c754be59c98d48505c23b1dae5c6011f3813dd", size = 1243575, upload-time = "2026-08-07T11:28:15.37Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/c1/ae7da218053120635a4ca802366c69f707203641af95372eeb83f70dfd52/ast_serialize-0.8.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7cc5f10386994c0f4844f1e6d6a97127e9b478660eb6dec2b257644f0acab64", size = 1294396, upload-time = "2026-08-07T11:28:16.813Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/89/271d1f49c5269fcddcc789ea3f25be401f6723fc1138aeda539f4d05516d/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6102f2f985c2e542be85cd857678ec9356fefa792b93cadfadd31139f5696f27", size = 1401987, upload-time = "2026-08-07T11:28:18.333Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/be/4e7d77fcf571ac7cb5cf7115a20c36642bd7d29473b45dfaaefeb9618f90/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:3a8660fe66667b76a6e9dccd1d33e66b229fde3b308db991c041609226c005b6", size = 1502904, upload-time = "2026-08-07T11:28:20.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/ae/ed1de2db7e019d4236fbc164ffa5ef9a6022a300a342bbf142d21b7c141e/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:e7266307e5fba39836edb79def8608887af48820508bff3c5f2941e1e04d1534", size = 1496967, upload-time = "2026-08-07T11:28:21.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/89/5fea507fae5c5f18b7dc7f95e5c00956574b8c717b8fd2049c504fab0b18/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca7e6fd1ad845d1cc649dc2ecd499db2f8f46af5bf8da7b70dd858774cc038b", size = 1559041, upload-time = "2026-08-07T11:28:23.194Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/71/478d69df21b64e064554a68134c94be304270316ca676a94e63c389a636a/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2880350b13d3eae69a0d70bc1fb6c9bfaca4dbd0e20ba8cd1aa483080b56ff06", size = 1417367, upload-time = "2026-08-07T11:28:24.601Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/2d/8962dc8d5b3a9dc27b36f9db199afa25264c741505469d9ec10ffbfd2ba7/ast_serialize-0.8.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:ab0f9a59f7d63d0d441b56b9a818b273705264352d5115cfee12e940e816d958", size = 1446178, upload-time = "2026-08-07T11:28:26.152Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/22/14d2ad4fd1d1bcd0dc687ca268e0630069f45162496260c0efb70ee0ea72/ast_serialize-0.8.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:0485a25ef519c62e749ee3c1ad8070e591b380d67226349eb5a70b228dc1ac4a", size = 1063811, upload-time = "2026-08-07T11:28:27.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/1d/84a327c0202a41aa5fdba3ade33904d6d8f3b9e6806fa83568d835395850/ast_serialize-0.8.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bd84d60bca7079e741be4ac5dbe237751a59d7f6f9f0126b11880d63822cbe16", size = 1105518, upload-time = "2026-08-07T11:28:29.691Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/92/74556dec52fde85a2ad84ed159991b916241043788609c15d8b77e14570b/ast_serialize-0.8.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:057769b5921336eb2d9124f2a731b42ed05ffdac559b840dbdf6f3937cf153dc", size = 1076319, upload-time = "2026-08-07T11:28:31.282Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/5d/c650b1f2cc1e75193358da95a080261422e8cd10b66d7370b1688c9915c5/ast_serialize-0.8.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:a02cbed7d8bfdcdee88edaac12bd50d53d9953aaa2e1852ef078625be5f1c0b5", size = 852914, upload-time = "2026-08-07T11:28:32.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/e3/6142e920fec6ef7bccabd8c24ed8ed99f8bdc6cb8b065e1df7c6a3b2d667/ast_serialize-0.8.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e1bd223df0f6c96b396975fa604cb33bce53d9b4a0185490be4c4a289f7c9c87", size = 1184007, upload-time = "2026-08-07T11:28:34.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/e9/6e8be8df02b35d85e2b8809f7f1cfa290bdf5882b55127a539d049482db0/ast_serialize-0.8.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ddd3b61f45c132da66c5476b281891e08c1fd87fbdabe8a6973e1622efc85f06", size = 1177588, upload-time = "2026-08-07T11:28:36.318Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/80/7e0fd2e2e2aba257820db4a8657c4c356844d36b914b20a4af294bcfb902/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f9caa63fad8241257ae401b5ff0a64026c6adb36b8e86cbe8782d9ea505daf6", size = 1234575, upload-time = "2026-08-07T11:28:37.772Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/6a/3bae0af06f9b1bae3001c44d64215f5b567877e7aae9ffd45db11c3a7647/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3926fa117b5e65019853a2969966d11c7175af377a3425991f3fe73784412405", size = 1236015, upload-time = "2026-08-07T11:28:39.14Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/c4/ce2d41a1bc22508e82618901f7e10f2a5e2f9556553fea90624daf9875e2/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:485f1113af805e9e170b95ef993ca3fbd4f89c04bab25c58b4fc632d854801ab", size = 1432808, upload-time = "2026-08-07T11:28:40.664Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/90/f5058f209756dd70e958b7538aaa82d25d24944baf9ec8ae6f27b06fcacc/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccebbed24f1281062d5852353c72c47502955926cfcb8345ffb3a44d87ff3d3", size = 1256251, upload-time = "2026-08-07T11:28:42.223Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/32/7f77ea87fa0836daab706ed5cb7f903bb25fa26a77439011aee626af11d8/ast_serialize-0.8.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:252f883290d1cdb728eb7fe1d9a7221b88af5a329aae0bc91ddee4dafb820331", size = 1258574, upload-time = "2026-08-07T11:28:43.751Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/5a/75b82ad2725b5e8e8c742732f9e76c6738a292d0709e1f60d10a973730b4/ast_serialize-0.8.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:96abc072ad29db8d02194afd47d68987322622787daceae82398d7b69f3ba2e6", size = 1254075, upload-time = "2026-08-07T11:28:45.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/54/8c20ed4eea805516a3fd23dd4a721ce28c64f50f0e4b359969f60a8c97a6/ast_serialize-0.8.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9118ad3e369727060b2696fc4078f250ecffca4248ba87f537f55cea9f9dce06", size = 1301018, upload-time = "2026-08-07T11:28:46.851Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/5b/9f14430f12fe830b656fb38f8e2e05ee13b02a88967660bef46af0ab22a8/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f359df4bd921918af8bebd142a376c77511d7151cc8ba852760b587b5a4a54f3", size = 1409951, upload-time = "2026-08-07T11:28:48.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/3d/084882eca93c842bd4262591a071ec7f825340644035e51501208cc5a8d4/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e94f9121d13fa36cbf21314783c77d05ae3a0868decd18cf5233fdcc6de49ac8", size = 1509544, upload-time = "2026-08-07T11:28:49.847Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/73/ea84852096c2036c61cc0b2f97b90242207419f534dc671060ee1c8e05cb/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:54f95b486018d262bcb387a9afd96f0da74508b442762b80c769454a6fbb3ee3", size = 1505671, upload-time = "2026-08-07T11:28:51.239Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/88/287b9a5300c1f2f651d259f670931b63110adc265b7613c885b44c5bc53d/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c38b915511e32bc718c49dbce98ff9af36bac0ad6a604f58000cd5e3aecdba7", size = 1563685, upload-time = "2026-08-07T11:28:53.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/f3/1bc3a79afcf0c2a8d2c37182d0d659d1545a9d7f7f6dc9cf3e63d6c17135/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:9a2ef9cf12f2de4f1028c42c1dd7d775255e0fb3e5bb48896c97e35ef52366fe", size = 1427977, upload-time = "2026-08-07T11:28:54.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/cd/440c798957e14e31776bfeb024d8fafe0bb1d5b89c51c2f067e69938f7b0/ast_serialize-0.8.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f18048fe9f6dd266bd577cdec48bdcecb74faaa01fe941324435483b013ed2a", size = 1454335, upload-time = "2026-08-07T11:28:55.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/4a/587eb36dcc240a54c8660f599464516b469ecad96f0dbdb6bccbedb50745/ast_serialize-0.8.0-cp39-abi3-win32.whl", hash = "sha256:31883542dd6c94d178f5db3d32fbd69c5eb88b3a7c018e7ac8cc0c45195ddbed", size = 1068858, upload-time = "2026-08-07T11:28:57.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/a4/3e887bbd92164e183cb6e412c6a3e9198ddd446d7fe405958293ef5ef49c/ast_serialize-0.8.0-cp39-abi3-win_amd64.whl", hash = "sha256:861794565b06337005c1447ef23103a3d5a627d08bdc827870d00d0b28ef5f51", size = 1111839, upload-time = "2026-08-07T11:28:59Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/6c/b400476d3ceba681ab929787edc9554f6d88fcc69435eb681b00fc0457a5/ast_serialize-0.8.0-cp39-abi3-win_arm64.whl", hash = "sha256:b2a5978662fd4db463dfb4b974d2b10ac6430b98f5333aabc7051909df3561d0", size = 1083655, upload-time = "2026-08-07T11:29:00.349Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authlib"
|
name = "authlib"
|
||||||
version = "1.6.8"
|
version = "1.6.8"
|
||||||
@@ -147,7 +211,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brewman"
|
name = "brewman"
|
||||||
version = "14.0.0"
|
version = "14.2.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
@@ -173,6 +237,7 @@ dependencies = [
|
|||||||
dev = [
|
dev = [
|
||||||
{ name = "bandit" },
|
{ name = "bandit" },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
|
{ name = "pandas-stubs" },
|
||||||
{ name = "pre-commit" },
|
{ name = "pre-commit" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "safety" },
|
{ name = "safety" },
|
||||||
@@ -181,33 +246,34 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "alembic", specifier = ">=1.18.4" },
|
{ name = "alembic", specifier = ">=1.19.1" },
|
||||||
{ name = "arq", specifier = ">=0.28.0" },
|
{ name = "arq", specifier = ">=0.28.0" },
|
||||||
{ name = "cryptography", specifier = ">=48.0.0" },
|
{ name = "cryptography", specifier = ">=50.0.0" },
|
||||||
{ name = "fastapi", extras = ["all"], specifier = ">=0.136.1" },
|
{ name = "fastapi", extras = ["all"], specifier = ">=0.141.1" },
|
||||||
{ name = "gunicorn", specifier = ">=26.0.0" },
|
{ name = "gunicorn", specifier = ">=26.2.0" },
|
||||||
{ name = "itsdangerous", specifier = ">=2.2.0" },
|
{ name = "itsdangerous", specifier = ">=2.2.0" },
|
||||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||||
{ name = "pandas", specifier = ">=3.0.2" },
|
{ name = "pandas", specifier = ">=3.0.5" },
|
||||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||||
{ name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.3.4" },
|
{ name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.3.4" },
|
||||||
{ name = "pydantic", specifier = ">=2.13.3" },
|
{ name = "pydantic", specifier = ">=2.13.4" },
|
||||||
{ name = "pydantic-settings", specifier = ">=2.14.0" },
|
{ name = "pydantic-settings", specifier = ">=2.15.0" },
|
||||||
{ name = "pyjwt", specifier = ">=2.12.1" },
|
{ name = "pyjwt", specifier = ">=2.13.0" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
{ name = "python-dotenv", specifier = ">=1.2.3" },
|
||||||
{ name = "python-multipart", specifier = ">=0.0.27" },
|
{ name = "python-multipart", specifier = ">=0.0.32" },
|
||||||
{ name = "sqlalchemy", specifier = ">=2.0.49" },
|
{ name = "sqlalchemy", specifier = ">=2.0.52" },
|
||||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.46.0" },
|
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.52.4" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "bandit", specifier = ">=1.9.4" },
|
{ name = "bandit", specifier = ">=1.9.4" },
|
||||||
{ name = "mypy", specifier = ">=1.20.2" },
|
{ name = "mypy", specifier = ">=2.3.1" },
|
||||||
{ name = "pre-commit", specifier = ">=4.6.0" },
|
{ name = "pandas-stubs", specifier = ">=3.0.5.260730" },
|
||||||
{ name = "ruff", specifier = ">=0.15.12" },
|
{ name = "pre-commit", specifier = ">=4.6.2" },
|
||||||
{ name = "safety", specifier = ">=3.7.0" },
|
{ name = "ruff", specifier = ">=0.16.4" },
|
||||||
{ name = "types-openpyxl", specifier = ">=3.1.5.20260408" },
|
{ name = "safety", specifier = ">=3.8.1" },
|
||||||
|
{ name = "types-openpyxl", specifier = ">=3.1.5.20260807" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -261,31 +327,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "charset-normalizer"
|
|
||||||
version = "3.4.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.3.1"
|
version = "8.3.1"
|
||||||
@@ -309,55 +350,52 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "48.0.0"
|
version = "50.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
|
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
|
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
|
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
|
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
|
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
|
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
|
{ url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
|
{ url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
|
{ url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
|
{ url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
|
{ url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
|
{ url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
|
{ url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
|
{ url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
|
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
|
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
|
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
|
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -414,7 +452,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.136.1"
|
version = "0.141.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "annotated-doc" },
|
{ name = "annotated-doc" },
|
||||||
@@ -423,9 +461,9 @@ dependencies = [
|
|||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -444,16 +482,16 @@ all = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi-cli"
|
name = "fastapi-cli"
|
||||||
version = "0.0.23"
|
version = "0.0.32"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "rich-toolkit" },
|
{ name = "rich-toolkit" },
|
||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/71/9f/cbd463e57de4e977b8ea0403f95347f9150441568b1d3fe3e4949ef80ef3/fastapi_cli-0.0.23.tar.gz", hash = "sha256:210ac280ea41e73aac5a57688781256beb23c2cba3a41266896fa43e6445c8e7", size = 19763, upload-time = "2026-02-16T19:45:53.358Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/33/eb/3b534c6f8e157f9ddbf2a153512307c886cad0b258739c200dd8ff8c4452/fastapi_cli-0.0.32.tar.gz", hash = "sha256:38024d2345275e1b37ce8848727a580d84901b570e96b3256d9d36a9a5039424", size = 26636, upload-time = "2026-07-16T12:16:58.678Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/68/89/19dcfd5cd289b306abdcabac68b88a4f54b7710a2c33adc16a337ecdcdfa/fastapi_cli-0.0.23-py3-none-any.whl", hash = "sha256:7e9634fc212da0b6cfc75bd3ac366cc9dfdb43b5e9ec12e58bfd1acdd2697f25", size = 12305, upload-time = "2026-02-16T19:45:52.554Z" },
|
{ url = "https://files.pythonhosted.org/packages/d5/53/56ae5ae17bb0a5d89d1d31e5320eb1865553ebbfbde91cdc4c221245f2a8/fastapi_cli-0.0.32-py3-none-any.whl", hash = "sha256:8dcc286fa32f01bbd3f65dd09cfd5a2540ed5f2230b77db7fd30978d6165f3c4", size = 14670, upload-time = "2026-07-16T12:16:57.297Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -553,14 +591,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "26.0.0"
|
version = "26.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
sdist = { url = "https://files.pythonhosted.org/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" }
|
||||||
{ name = "packaging" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" }
|
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" },
|
{ url = "https://files.pythonhosted.org/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -621,17 +656,24 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httptools"
|
name = "httptools"
|
||||||
version = "0.7.1"
|
version = "0.8.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" },
|
{ url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" },
|
{ url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" },
|
{ url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" },
|
{ url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -699,36 +741,72 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librt"
|
name = "librt"
|
||||||
version = "0.8.1"
|
version = "0.15.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/36/9b/356320fbae2ac8467e21c5e73e1389c80468e4998c62cc7d3536cc51b614/librt-0.15.0.tar.gz", hash = "sha256:4e66cbe84437497d951b799d3e1551291b6fb3d643820a7014b3655d57a59162", size = 214338, upload-time = "2026-08-07T10:49:42.663Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/93/949053fb462eecc4a9a5ee770a81f4b40be7b79538b245545d4aebc6b58b/librt-0.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5de7feedc56337a088eb15cd9fafa9938367362221d8cc62c642b7f94821993", size = 149833, upload-time = "2026-08-07T10:47:39.86Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" },
|
{ url = "https://files.pythonhosted.org/packages/61/ca/8281aa6cd560a3420e4497729f6b704b53be3eeaaef82d5aeadddaf7441f/librt-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c0eb900c0e91f4aebe680845242e614f1864edfd44106380d0752ac29522bf8", size = 154088, upload-time = "2026-08-07T10:47:41.065Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" },
|
{ url = "https://files.pythonhosted.org/packages/dd/02/1a1662dceaba6a086360891448d5ce9a7d3555976cae59a31a39d744b9c7/librt-0.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8c9a650a188e38bac005048cbe6342e81407782944d01934540ab75e417df21", size = 494215, upload-time = "2026-08-07T10:47:42.388Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/84/99211619dc656370a3740c33d2b0b6d5a3fb1e73689314f6ed477a397dc4/librt-0.15.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:92bfed8deec93df30286b9fe9e3b1dd17329cc076a192b4ee5ec223841d54953", size = 491173, upload-time = "2026-08-07T10:47:43.683Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" },
|
{ url = "https://files.pythonhosted.org/packages/d4/aa/5448d0b05f4579b635d3899176817ebf561af0e57bacd425b5b1887264c1/librt-0.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec4b19788f835711a2072f9dbe6b03b3bf32ed1f0fb30cf399bdd59d9f0c33fa", size = 505512, upload-time = "2026-08-07T10:47:45.314Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" },
|
{ url = "https://files.pythonhosted.org/packages/95/82/01940e40b83c43a546c4a3c896cf34ca272a9690899d55914e4827b3dcce/librt-0.15.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4c7bacb70930f3d0a56f4ecf1be474a1f0d941b01dd73b756f3c256d42cb879", size = 523073, upload-time = "2026-08-07T10:47:46.66Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" },
|
{ url = "https://files.pythonhosted.org/packages/88/fa/759c0030f3ee371439eb26de34fc745807caf0abb878af7af4b8b7c3dd3d/librt-0.15.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e79f05e4a08b4d880342673312bbc895b56df7765605796f15902eb5367d3ae", size = 515080, upload-time = "2026-08-07T10:47:48.319Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/27/894e072228fcb159703c655da69f8cd10dbed489c36e3df7dd032a2483be/librt-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a417149c0cba4d50b61e992e5a15e69eaf96746609b461cc4ed168aeef6b79dd", size = 534164, upload-time = "2026-08-07T10:47:49.875Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/a3/0078e91c1f36f8815db17827de15650b9a3fe56c55fbf998c854b34e40d3/librt-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:da7a94d6a3411f579d72aa3e3bc5fbca7ed4549f3dbd7e5de3aa567333374285", size = 540616, upload-time = "2026-08-07T10:47:51.408Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" },
|
{ url = "https://files.pythonhosted.org/packages/86/33/81a29b796dd52a45e9ef7974c7732926e8f10f15b8d2be505665979f896d/librt-0.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:856f743ae607f2c1380eccb566c0038a9fb3eabf0fc2be2704d76d9f73557239", size = 545890, upload-time = "2026-08-07T10:47:52.818Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/82/8be1baa1350e5d30cfd70ae79d0a6f4dc5862ef47f7bb2808aabc9bb86e5/librt-0.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:779a6e7c894737e5983e7790a9c78c4000c30e23c9aada08081bdbea53b0fa60", size = 523287, upload-time = "2026-08-07T10:47:54.165Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" },
|
{ url = "https://files.pythonhosted.org/packages/c6/4f/d1be6a01a35c20ef734e0e44113f87d4af756a9354a89dcfbe3b4f8af5e1/librt-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96bb17dbe8bab3c0954fbebfc69ed395599de75b6bbc35e3270a878e15d4dd65", size = 565868, upload-time = "2026-08-07T10:47:55.566Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" },
|
{ url = "https://files.pythonhosted.org/packages/67/88/649cfa33f5825927b160610f670bdab012a64d627eddb94fa795ea4292fd/librt-0.15.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7220697efaa6e5348fc3d18ee7f8563d4bfecd9872b37ffb915bfc1d08840622", size = 81619, upload-time = "2026-08-07T10:47:56.886Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/31/8e88a8d5e48fc8d1a817787fb6811dfff6499acd6c8683dd83934aa6ede0/librt-0.15.0-cp314-cp314-win32.whl", hash = "sha256:f54598964d357b1c5ab77cf5d92f21e598fe0e23cdbe9618480807f81b4eba15", size = 100138, upload-time = "2026-08-07T10:47:58.093Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" },
|
{ url = "https://files.pythonhosted.org/packages/80/92/20fd6c4b6a1b1a564b076d55cd3d427d8428217d7638dc25a654cc4791d4/librt-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ff5893a2c23d886aa9ce786de5ac6ddc74aeeaf90743682b74d920e117d2e28", size = 121258, upload-time = "2026-08-07T10:47:59.564Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/28/6af430b44d9ebb897b865a3c363b6dcace51357be2347cc0f8f869656a86/librt-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:3722a099730704c9a3d70c879fc0f51daec25fe5f1555672d97bc595abeafb95", size = 106467, upload-time = "2026-08-07T10:48:01.097Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" },
|
{ url = "https://files.pythonhosted.org/packages/7e/aa/b42bb798942ced219f6d63b27e07f91237887a8d0bd0921666db79a13790/librt-0.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:38c0c7d4b6fc06c3324b3f9162c8391bfc4fd9dde53afe1033ce7edb48d5a714", size = 159523, upload-time = "2026-08-07T10:48:02.442Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" },
|
{ url = "https://files.pythonhosted.org/packages/75/03/1b53cd4ef904e73b1d828a5f90143bf94a2967d7cfff0b9ccf93e12aa9b4/librt-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b2fdd7ead3c995c37940a790690660d0ca006c302db26cc51933f6766866fc3", size = 161638, upload-time = "2026-08-07T10:48:03.725Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" },
|
{ url = "https://files.pythonhosted.org/packages/ac/c4/9f9c9fba097d49e9e694c2b4dc331df31884645ecbc58a93b4b5fc69d2c5/librt-0.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fde98cf1fc4bac144ce23c2c4c017b924ba714509ea9334977b0b27050c837d", size = 701795, upload-time = "2026-08-07T10:48:05.135Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/05/0966840bda0380c8ae167b9043c6230202941cc90ea29c48e096964c765e/librt-0.15.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e3b461183c5fa7681b48560f91515f53a953122fb30c71e07abc67d7ddf58c38", size = 682147, upload-time = "2026-08-07T10:48:06.555Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/af/1c47ca573c30ea47d195aec26133af522fea1104afaace028d7b32247ea8/librt-0.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4bbcc257e3babea20a91715c361b24554ec4e8f51aa578568afc230799fe1a19", size = 696397, upload-time = "2026-08-07T10:48:08.03Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" },
|
{ url = "https://files.pythonhosted.org/packages/2e/0f/1aed6223d4f9f9d1171a8596ff100ea4c3f7699fea7a4ba657c3e60daa6c/librt-0.15.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b845b8d48088fad0cadc84be4b8fda63203be7e9237b71015b3925443c1f35ab", size = 722542, upload-time = "2026-08-07T10:48:09.569Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" },
|
{ url = "https://files.pythonhosted.org/packages/c6/22/9e3a929aea456c97d69e6ef3884efea56d4807f97399471cc946baebd8af/librt-0.15.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b30e600e8f337b9bd7f39b86d9fdfedc73cc46e3d0f745931a23a234220bb7e2", size = 729709, upload-time = "2026-08-07T10:48:11.129Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/1b/c327ef6018e3a9ca0b8e7c5eddeeb331ba8f9b76c24e126d37d0f6d62faf/librt-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:64b0c8c35aa4c4ed79896359f3e0b285cbe4e610042106500da4811c322cc108", size = 752891, upload-time = "2026-08-07T10:48:12.558Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" },
|
{ url = "https://files.pythonhosted.org/packages/d7/d1/d5f1ea02c56930087009e39db9b70660a663e76c730b27b925d786718457/librt-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0da0d94cb802f32a0524653e7201f2cef72d5f700a5407678f5290483d4fcd08", size = 745301, upload-time = "2026-08-07T10:48:14.55Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" },
|
{ url = "https://files.pythonhosted.org/packages/d9/3c/5f7c585d15ebb2250c73e7c0ee4e9e47be72c65d520c07ddbcdc62037674/librt-0.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4a6369168d371207339b1e50d4532b06a7121586141f82599505a3f315751d47", size = 747921, upload-time = "2026-08-07T10:48:16.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/52/1443a446486eba966bcbca1696b472e4f210320ec42f490a47f48fbf0fdc/librt-0.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c434e072557ade9cbc642d052c89d031efe47d5c9614523619d0d74a02378e81", size = 727561, upload-time = "2026-08-07T10:48:18.089Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/91/2270a9380f11725cf83ce1925a5e32dd1dde2be9bba597f25c10a38644e7/librt-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7eec6a42018bc1d45763b1c162d3d2bf7c3b9a1b0ed30d3e91dcba390efefcc", size = 774417, upload-time = "2026-08-07T10:48:19.611Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/3b/f4b1548d4f5b99186737fe27aec238e9823e8d5d23bf4df007c030689dc5/librt-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:6912fa5e635d74529ac7cdb1bdf6ca3af4453da8d1edbe0110ee1cb4ad407ebf", size = 104381, upload-time = "2026-08-07T10:48:21.048Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/b6/134afad262def1de04c0843c376d02135f1168af43f22e09a52bd8394727/librt-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8e11699ed745931c395acd3621b07062e0f840efa6935aad87a64ed0995f0915", size = 127034, upload-time = "2026-08-07T10:48:22.561Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/5f/1b6846b20572bd699c9e9ec321a5f781845bee477df2aa2a43b28bc40119/librt-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5d2a91724463bfed4f573cd7a9fdc856d2e230d0c0e5a61416a93481dccd8605", size = 110827, upload-time = "2026-08-07T10:48:23.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/44/4de9f4ddadb009a55c7758eb5736d62534a7daaf27bd71bc50e64b606b06/librt-0.15.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8443e38dcfcfdbcf5add5118c623efd788d65ac2e25756d6251a54a06a4d0aca", size = 149843, upload-time = "2026-08-07T10:48:25.148Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/eb/5d9ab71e30119c44094e0275f38b47dd327aea0f843a080396677029d508/librt-0.15.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6d15a29033c57490cfe2069097c6fc4049e4e65ffbb749be7dc453b7c4c68965", size = 154510, upload-time = "2026-08-07T10:48:26.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/9c/8505d1b8f5e8c19587bd03f7429993b3e9ce5c06819d856bfb11d919374c/librt-0.15.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c05c729b589e734c09578bf5964be48a911765484840d017bbc84f49d4c4ad", size = 497543, upload-time = "2026-08-07T10:48:28.045Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/9a/3a8390775cb095765aded027ac9c63e7c8ea74e731498607544c6505de0e/librt-0.15.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fa60887537e1d0cd2d9982269d33a709bf54b195cd2b9364fc0a758022af5bd9", size = 480452, upload-time = "2026-08-07T10:48:29.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/40/258a4a7117ee915d66de5cd9b8ade65a440993161107ce3a686f1859955c/librt-0.15.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8bc24219b24c0af375718942ab75e3544b2763085f40f965be4326734ae8328", size = 507768, upload-time = "2026-08-07T10:48:31.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/c6/2f4dd296c97a0b85b98894519b279408ec9dd602d4f692b1ea0e25dee670/librt-0.15.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86a21a7bd3fe3a419512ef424cc1c020f6771d0b29cfddff36d1635a855e63f0", size = 525122, upload-time = "2026-08-07T10:48:32.7Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/dd/29eab42be13b2bf0ea8cb227135a45d44693e30a7e8b92871981ff56b82b/librt-0.15.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbab647e88d90b3167b91efe7091e248653688ed4337e4f90907a722c7361bb9", size = 520371, upload-time = "2026-08-07T10:48:34.294Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/ed/4bad71adeca8fe208b775c2a35417fa5a2584c8f4791daaf89a89450fea1/librt-0.15.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d8edcf6f550e918dca779c069b9e156385c60b406f99fc7641f32c52f7193659", size = 537258, upload-time = "2026-08-07T10:48:35.88Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/63/59dba6143fdcc7240c54458b629f3250000a61b8945890fc9efd451b19c5/librt-0.15.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:8b62076030baa2d8b1501a46bf0e19c27a489aa90671c55665bff7887f7660b0", size = 527432, upload-time = "2026-08-07T10:48:37.466Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/21/21a24c6a2327d8362580efebe77286bf47b0f4062ec5ea41766e609d3c7d/librt-0.15.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:d00d20d1818e82a07a0ee0aa89a98b17ed7916b92441090b683719cb20a59b6d", size = 548108, upload-time = "2026-08-07T10:48:39.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/6d/fc68c89a7971418b41f9a873623ff935cb864097544c6a2f8ce491c8ef5d/librt-0.15.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4e6ee93fc3cf848dcbf0cce2eca73d8e7dcd0cc2b6df3a529d57750b30a4c55c", size = 529681, upload-time = "2026-08-07T10:48:41.392Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/7e/c2d98766124400d722063a630b0fde38a9fc768705d37eecca15c47dc192/librt-0.15.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:32896a0af72508ea979e0acb4e4c04cbeeae04938167950d535c83c45597167d", size = 567736, upload-time = "2026-08-07T10:48:43.124Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/6c/f8c34a95e3a515c6e1c192b89511e7253c89a7760c6b500d57ffdb8d2dc8/librt-0.15.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:ec3ba415afaf951f6951b1dd16d3c8e4f540065fc382d7e70b823a79567ca374", size = 81673, upload-time = "2026-08-07T10:48:44.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/9e/e23fa8e78679ec45728188650b39e8ff476c83b691c96f749217df3b1b7c/librt-0.15.0-cp315-cp315-win32.whl", hash = "sha256:d2813ba2503764f0450680c533d13df7cff9b49df1411062eded5f67db4195b9", size = 100081, upload-time = "2026-08-07T10:48:46.171Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/dc/3eb4c5e297343f0620a55532cd7c8d764d3001fa2159212dadf480464827/librt-0.15.0-cp315-cp315-win_amd64.whl", hash = "sha256:b87d67e33afaf265262f2a66db578284b88ee2e6fcd224579cb5c15518677ad8", size = 121228, upload-time = "2026-08-07T10:48:47.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/70/43abce19f04e49762f8ec834c8fafee13cc40fd6b94a72a24e534febfcd0/librt-0.15.0-cp315-cp315-win_arm64.whl", hash = "sha256:713bd7df21170b982e729e46870f31d6b437bd1a9b4648cffb529bd3c2ec5c4b", size = 106487, upload-time = "2026-08-07T10:48:49.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/15/83f2deddb9368b8951ec8c9477269b5b9b8bd9bbf15e57402d0f38817dca/librt-0.15.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:3de789c82752730f94782a5ee518baf9c05edf85733aeaf73bb6e518755cdf54", size = 159448, upload-time = "2026-08-07T10:48:50.649Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/bf/043097353f9b3c73b583d07f6b8e552795463f4bfc8caf85e42eee50c26a/librt-0.15.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:e0b5deec9a8664eb722c797241970fd4aa1894d25fda36a1ddac0f7407606bd6", size = 161686, upload-time = "2026-08-07T10:48:52.174Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/2a/8ae77f9719d42ce71cd708560a3557b38ac3c17a0383e57f87084de45bbe/librt-0.15.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5563302a8359bc2295bb7084d1a8ed1519df96afb30eb2aa4e0bff7b54228988", size = 710668, upload-time = "2026-08-07T10:48:53.782Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/34/c0436ea134deb9a0d6da80a396a2739a81cb31e0418f7227239e23140898/librt-0.15.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:22d6263b9d39d7bbb286fa791945646e3218f1be2d693e36fb630f1d0e59cd13", size = 679396, upload-time = "2026-08-07T10:48:55.645Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/9f/001e0d99aa9250d5cd5715a9081291a20656083459f9019cda15255329e1/librt-0.15.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39ffd14646190c454f0d86e0d256b33f00a87a26ab410e619773b841d0e41416", size = 704313, upload-time = "2026-08-07T10:48:57.46Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/53/b34fa9d0ff00f136f4d58ebb4c411ff634baed1eb412bb602a2bc8dcafcb/librt-0.15.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47318cd3a61401452de11282242937e3e057c4fd3dbaf601e269d0928a06c0a", size = 729847, upload-time = "2026-08-07T10:48:59.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/ac/fa4d7a424665040e95baf480a6d523446057684b6758624c85338e8a23b2/librt-0.15.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a56a1d4f859a82ca5b99fc4b82c9b027b15e3c455c5cd99e7d0719f27bb20b6c", size = 742736, upload-time = "2026-08-07T10:49:01.151Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/f1/e17a9bb5de6fb8c3186ed1a7d68d21618b027ac2d3633e03d3b6109c67ae/librt-0.15.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:077471b3182db4e17c36ae91555f36a4d2c00080b267f749bcad34a478a9a302", size = 763454, upload-time = "2026-08-07T10:49:03.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/ec/ecd02cd30935b931b9cdbfed6ab5a099c51b280b4e7baa274da80978ed27/librt-0.15.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:411ca4d1b905b860ceba7570dd6717a71dedaddcc4b0f77ece710aa41ee11f8d", size = 743296, upload-time = "2026-08-07T10:49:04.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/b5/b3c2b8353ce820a4854f78d19321344242f89fa71c975b71132ba9bf242a/librt-0.15.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1256589e0b0adb31751d685a68bce29d73407ddf4ef05d4188f49d5dcf9566d9", size = 756217, upload-time = "2026-08-07T10:49:06.825Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/52/6cc22542ba59146b05cca2a656f9ff8bb67e38e63d12c3b0cc183d837bf1/librt-0.15.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:f42b74a53e5f26a0ba0007411a7455b66c67ce4022a39cc1f56fc4efd65bcbab", size = 741934, upload-time = "2026-08-07T10:49:08.839Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/32/a04b72b1aa86e3be23b2ecff8c1aad2dcc955bd3956d6d26e7e34267e57a/librt-0.15.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:291bf73caf78b9e88d6fae9bfd693207ff7d832e2fdbe2cf8e746bc13f5f892b", size = 783763, upload-time = "2026-08-07T10:49:10.661Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/f0/89eb11dffbe9279ff37144dec786927314502ae0b114f1449dc78c458aab/librt-0.15.0-cp315-cp315t-win32.whl", hash = "sha256:c16d15ee371643ab48dc8248a3e680ebbeca573a13af2c3dd0c985b142d77162", size = 104313, upload-time = "2026-08-07T10:49:12.305Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/4a/1f1978c200f563beda63c36adff2d65bbecb81e365e8e69e572f5f70fbc6/librt-0.15.0-cp315-cp315t-win_amd64.whl", hash = "sha256:dbd605739f228912dc49027cb764456b9757750bdc2b6b7773164db7096c6fd1", size = 126889, upload-time = "2026-08-07T10:49:13.881Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/a6/800800bfed7b1fb10fc3f3d557785c3854e80d3f7a9800d784b176a1fc2d/librt-0.15.0-cp315-cp315t-win_arm64.whl", hash = "sha256:84d244b00604d17df3fc7736c327892d6bba66181254aa4087be807b6c342bdc", size = 110700, upload-time = "2026-08-07T10:49:15.499Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -805,31 +883,44 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.20.2"
|
version = "2.3.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "ast-serialize" },
|
||||||
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
{ name = "mypy-extensions" },
|
{ name = "mypy-extensions" },
|
||||||
{ name = "pathspec" },
|
{ name = "pathspec" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/82/6a/878cc1097d4035f82bd516658d0c528d2a9955bc7b363afcbd0b07fea11b/mypy-2.3.1.tar.gz", hash = "sha256:47c1b1207258513a9d93495f69c8be9de73916186f0e52703e8c461b7a623419", size = 3992554, upload-time = "2026-08-15T03:03:38.549Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/c4/42a49d44aeff804edf1b19acce0b49e8bd1a9c57dee9605dd8d980aa43d7/mypy-2.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:192abaedf75da1bc0b1cef104927e70ec49c1ef0031cc4825c7ee10a438ed24d", size = 13986778, upload-time = "2026-08-15T03:01:33.69Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/13/9331fd2dfed7194d66c5304072894a8be3e51e9deda6863c1eceaa35a43d/mypy-2.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf678dffd16efcda2c15cbd30e9ecc0081388e29ea23687a88e686ed92638dc3", size = 14188467, upload-time = "2026-08-15T03:02:40.554Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" },
|
{ url = "https://files.pythonhosted.org/packages/78/f7/f4a34edab45667c5465855dc585a20e87978ffa8aee711445b7239d120c6/mypy-2.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e036f06b41630f4c8a1d48f9ac6aa26acc65f8be089973f5519da643318f03f", size = 15225538, upload-time = "2026-08-15T03:03:09.761Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" },
|
{ url = "https://files.pythonhosted.org/packages/40/05/534b3590757bd05794f73e07f6666c2a77b8597ffed795c94ce570096aa0/mypy-2.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71af9c8a894e862b58e92abb08e53b05a384a1e5e5d6dc7cda59126211a53d82", size = 15480805, upload-time = "2026-08-15T03:01:41.134Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" },
|
{ url = "https://files.pythonhosted.org/packages/55/da/bdfba852e2562f599624af5bb7d29e36b0b4f526f2b8bac85efe0dd1803d/mypy-2.3.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3c80cd23d85368bdd9f37d5231dfd97d35bcbf5bf41af96ef3a9b078ad1957f9", size = 7761712, upload-time = "2026-08-15T03:02:36.008Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" },
|
{ url = "https://files.pythonhosted.org/packages/98/31/60fc64a74cdba4f2a5d642d32317993e479163e1ac7d91b695e5d15e2264/mypy-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:4956f34d145e145562a0a0bf367f642bbc85c04ec2baf47ae015947c3169a85d", size = 11423968, upload-time = "2026-08-15T03:02:06.931Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" },
|
{ url = "https://files.pythonhosted.org/packages/a9/23/eb5950b24cd26ba3b78f87707a275568d633c77dae8e61c9661be6055ca6/mypy-2.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:cfb12e360242d23d91f5e978d94f58ea66acf5804c4fb6f2f794a20d4cb1b595", size = 10399323, upload-time = "2026-08-15T03:02:33.671Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" },
|
{ url = "https://files.pythonhosted.org/packages/82/c7/f80f4e46c0b9a00eb5f78a79d49dda8bdf56a5230f7257fb33e76be04da7/mypy-2.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5f1c50bb05b64e2026b52867e8d21106f01313c744a2c4ecc34c90d12e8d6e2", size = 15121308, upload-time = "2026-08-15T03:01:46.053Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/74/9b04f17c7074cc5188f02fb63a2ca1d43fedf479e84fe3091c39061a1d7f/mypy-2.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:667196b352f4cf304ded4c10f90cfc179263a1acfb3cdcfa984bdfd340d498bc", size = 15536590, upload-time = "2026-08-15T03:01:35.941Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" },
|
{ url = "https://files.pythonhosted.org/packages/26/04/c837ef6208e567774e2ed1f863f8ba6ec4817b1b6dd426315e5d559b6ec9/mypy-2.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9c53e395c12cad2c6d4b67d5da7c6057638a132d85c08b73646b18f802a0045", size = 16791074, upload-time = "2026-08-15T03:01:31.073Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" },
|
{ url = "https://files.pythonhosted.org/packages/37/68/48730230afa45192d5bd429a6a2ff24a6f8dedda90fdf2b221792b54518f/mypy-2.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:18162b128c3f9c703cd35f5537446900b0d21a2549aa7a95d21380d2ef643fb0", size = 17069183, upload-time = "2026-08-15T03:02:28.566Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/ea/ca23fc9c20eeda09a15c9cbcf50015d0e73f409f6ead059e42aa69a608ff/mypy-2.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:30c0477d4aab7b7f39c8397dc877f2c96b9fe5588ec379f372c56eb63d599f63", size = 12154679, upload-time = "2026-08-15T03:02:04.809Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" },
|
{ url = "https://files.pythonhosted.org/packages/3b/67/8d982126034990869466f73b8db80dcb2234a7ac39b4dad093e047a79835/mypy-2.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6941ab3619377bc3f32ca02876b07d27f216f5201604b664d3937ea0fdd23bb4", size = 10969159, upload-time = "2026-08-15T03:02:38.152Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" },
|
{ url = "https://files.pythonhosted.org/packages/ee/f7/41e7f2d8117fbc7a7587286162ffe2f688984b69c46ed63cf5f2e4fc3bae/mypy-2.3.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:6f041a6de52c9217ca125e78ba0a335cb7fd98a1c0580978e49ab2b126f70b57", size = 13990694, upload-time = "2026-08-15T03:03:21.919Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/85/8f665811a0c8f3bf6fa1d9acd665ec2d97a2bcc453ae68dcd92340941cd6/mypy-2.3.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5159ae60f5dbc3a498af5ba8365505808ac8031bc63f9e00304ad545d40bdd9b", size = 14203518, upload-time = "2026-08-15T03:01:48.455Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/82/91b866c8546b120bff83b73a439d90d2d63ef3aff113599e6b8e4d566848/mypy-2.3.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a8a7a0a7f6f6e63995c0ac36fa0c07b127413fdc81f0439b7f3dccafd33561", size = 15220224, upload-time = "2026-08-15T03:01:23.577Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/78/c226c99208ee40de7c768369fa533f933afa003dfdc606ff021450724e91/mypy-2.3.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2329c0501293d4e1f33bc15d04d6304d65a1cdda967ee93a05c1e681a3923133", size = 15501512, upload-time = "2026-08-15T03:02:09.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/e7/7cfb3f106c393979f4cc37ad6c0586044d50401e3c35b0c003e4f3ba6bc9/mypy-2.3.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bb26deed807bdb0457cf3e3f1cd7c4a1cf9d66864eaf1b4a61e06805d4c6b1f9", size = 7761913, upload-time = "2026-08-15T03:01:55.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/3c/52affefa273b97939a1f474ae4a349c8718635c15b941112dfab4291b0c1/mypy-2.3.1-cp315-cp315-win_amd64.whl", hash = "sha256:375d7013876a8233b2d05be185bfa09f689696cd999ce8b1cfe6acac5c80e8a3", size = 11422533, upload-time = "2026-08-15T03:03:24.101Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/b7/75643e70c72a5b346d8a9b1543c967ea8824df2ee3fb7ccba652c272b7bb/mypy-2.3.1-cp315-cp315-win_arm64.whl", hash = "sha256:586b3612214cceabb3c0f588c97e7d1e535393f06a60e912e994f6b3ace97523", size = 10397931, upload-time = "2026-08-15T03:02:55.265Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/ce/53be21f2d4adfcd26f63f1184a13ed797015ab463853f117e2e11e4d726f/mypy-2.3.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ef0c6335cda9d807f8193d8ff6204a72bc909fa9882aacbca14f43cdb7188306", size = 15118669, upload-time = "2026-08-15T03:02:51.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/43/20de757cd42989d291a17fad607742c4c74e875ce5cea00e5a5225020ac1/mypy-2.3.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e598c8c66401d26b150872154a286e6d484cf2789c3bb28a7556806298423021", size = 15545627, upload-time = "2026-08-15T03:03:05.132Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/fc/092bdf77ad280eaf501422f0f3b966012b528076cc13e41a774861c907d1/mypy-2.3.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eda22fd4efa9dcd39331d1dede9b5b8b8a7fd69af07592e778433da98610d29e", size = 16764157, upload-time = "2026-08-15T03:02:23.958Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/5c/c94c4d62d909b07f552d0d9356d7acc943825558e602a64822ffa2231536/mypy-2.3.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:2a0ba2e57847849fb0d1fcdabb32786d223095ed8bc121dfe322bcdb3d9c46bc", size = 17073258, upload-time = "2026-08-15T03:02:14.573Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/f7/511a88b89e478053c02d22039bb8f3ce4183efe8fd7a4f0a5910a8bb0a32/mypy-2.3.1-cp315-cp315t-win_amd64.whl", hash = "sha256:3f7e865dd51f235f60a2dbcd8728a1c095f5ca28f095d48a725b84cd935735c4", size = 12135505, upload-time = "2026-08-15T03:02:16.714Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/bf/02573b56964ecb0f7c644f915f53c325ae15c3faec521c5adf11599a32df/mypy-2.3.1-cp315-cp315t-win_arm64.whl", hash = "sha256:8ad80807dc3ab8ea978b1b2b6e4a657194ace1d4ef03e0e731aff1abd517da29", size = 10962647, upload-time = "2026-08-15T03:01:43.712Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/41/9675c7a1e78edecfba0b79e587a52594c56e189368261dc7b3a7fffb9527/mypy-2.3.1-py3-none-any.whl", hash = "sha256:6ed5c7e3419083268e5c9258bd1c1ef91af44a9e89374dbcaf37b775716e72eb", size = 2754338, upload-time = "2026-08-15T03:02:53.4Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -917,31 +1008,43 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pandas"
|
name = "pandas"
|
||||||
version = "3.0.2"
|
version = "3.0.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "python-dateutil" },
|
{ name = "python-dateutil" },
|
||||||
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
|
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084, upload-time = "2026-03-31T06:47:43.834Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146, upload-time = "2026-03-31T06:47:46.67Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081, upload-time = "2026-03-31T06:47:49.681Z" },
|
{ url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535, upload-time = "2026-03-31T06:47:53.033Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992, upload-time = "2026-03-31T06:47:56.193Z" },
|
{ url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257, upload-time = "2026-03-31T06:47:59.137Z" },
|
{ url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893, upload-time = "2026-03-31T06:48:02.038Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644, upload-time = "2026-03-31T06:48:05.045Z" },
|
{ url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246, upload-time = "2026-03-31T06:48:07.789Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801, upload-time = "2026-03-31T06:48:10.897Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643, upload-time = "2026-03-31T06:48:13.7Z" },
|
{ url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641, upload-time = "2026-03-31T06:48:16.659Z" },
|
{ url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993, upload-time = "2026-03-31T06:48:19.475Z" },
|
{ url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274, upload-time = "2026-03-31T06:48:22.695Z" },
|
{ url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530, upload-time = "2026-03-31T06:48:25.806Z" },
|
{ url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341, upload-time = "2026-03-31T06:48:28.418Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pandas-stubs"
|
||||||
|
version = "3.0.5.260730"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "numpy" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c2/d2/dea4a3a56b7b5f69c5fbca9f14625fcf28e1a39a657e9833d4a10bcac593/pandas_stubs-3.0.5.260730.tar.gz", hash = "sha256:f70a232c57d93a5a2c81f8a53953e10891a5374bc92652277deb325e2e4d0ff3", size = 114631, upload-time = "2026-07-30T14:31:42.271Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/c2/959caec5c46f484b5f8bb6def4b0cf7a45ba6acda26f12b75142d98cc5ae/pandas_stubs-3.0.5.260730-py3-none-any.whl", hash = "sha256:60e90e3e1eda6937e337e243cbe6217e151c11137cd7eddf832af537c7310bfd", size = 174807, upload-time = "2026-07-30T14:31:41.17Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -978,7 +1081,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pre-commit"
|
name = "pre-commit"
|
||||||
version = "4.6.0"
|
version = "4.6.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cfgv" },
|
{ name = "cfgv" },
|
||||||
@@ -987,9 +1090,9 @@ dependencies = [
|
|||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "virtualenv" },
|
{ name = "virtualenv" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1053,7 +1156,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.13.3"
|
version = "2.13.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "annotated-types" },
|
{ name = "annotated-types" },
|
||||||
@@ -1061,9 +1164,9 @@ dependencies = [
|
|||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -1073,43 +1176,43 @@ email = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-core"
|
name = "pydantic-core"
|
||||||
version = "2.46.3"
|
version = "2.46.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" },
|
{ url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" },
|
{ url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" },
|
{ url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" },
|
{ url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" },
|
{ url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" },
|
{ url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" },
|
{ url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" },
|
{ url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" },
|
{ url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" },
|
{ url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" },
|
{ url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" },
|
{ url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" },
|
{ url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" },
|
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" },
|
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1127,16 +1230,16 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic-settings"
|
name = "pydantic-settings"
|
||||||
version = "2.14.0"
|
version = "2.15.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "typing-inspection" },
|
{ name = "typing-inspection" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" },
|
{ url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1150,11 +1253,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyjwt"
|
name = "pyjwt"
|
||||||
version = "2.12.1"
|
version = "2.13.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
|
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1171,20 +1274,20 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.2.2"
|
version = "1.2.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-multipart"
|
name = "python-multipart"
|
||||||
version = "0.0.27"
|
version = "0.0.32"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" },
|
{ url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1270,21 +1373,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/3a/45/affdf2d851b42adf3d13fc5b3b059372e9bd299371fd84cf5723c45871fa/regex-2026.2.19-cp314-cp314t-win_arm64.whl", hash = "sha256:a09ae430e94c049dc6957f6baa35ee3418a3a77f3c12b6e02883bd80a2b679b0", size = 274932, upload-time = "2026-02-19T19:03:45.488Z" },
|
{ url = "https://files.pythonhosted.org/packages/3a/45/affdf2d851b42adf3d13fc5b3b059372e9bd299371fd84cf5723c45871fa/regex-2026.2.19-cp314-cp314t-win_arm64.whl", hash = "sha256:a09ae430e94c049dc6957f6baa35ee3418a3a77f3c12b6e02883bd80a2b679b0", size = 274932, upload-time = "2026-02-19T19:03:45.488Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests"
|
|
||||||
version = "2.32.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "certifi" },
|
|
||||||
{ name = "charset-normalizer" },
|
|
||||||
{ name = "idna" },
|
|
||||||
{ name = "urllib3" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "14.3.3"
|
version = "14.3.3"
|
||||||
@@ -1361,35 +1449,36 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.12"
|
version = "0.16.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
|
{ url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
|
{ url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
|
{ url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
|
{ url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
|
{ url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
|
{ url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
|
{ url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
|
{ url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
|
{ url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
|
{ url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
|
{ url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safety"
|
name = "safety"
|
||||||
version = "3.7.0"
|
version = "3.8.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "authlib" },
|
{ name = "authlib" },
|
||||||
|
{ name = "certifi" },
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "dparse" },
|
{ name = "dparse" },
|
||||||
{ name = "filelock" },
|
{ name = "filelock" },
|
||||||
@@ -1399,17 +1488,17 @@ dependencies = [
|
|||||||
{ name = "nltk" },
|
{ name = "nltk" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "ruamel-yaml" },
|
{ name = "ruamel-yaml" },
|
||||||
{ name = "safety-schemas" },
|
{ name = "safety-schemas" },
|
||||||
{ name = "tenacity" },
|
{ name = "tenacity" },
|
||||||
{ name = "tomlkit" },
|
{ name = "tomlkit" },
|
||||||
|
{ name = "truststore" },
|
||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/e8/1cfffa0d8836de8aa31f4fa7fdeb892c7cfa97cd555039ad5df71ce0e968/safety-3.7.0.tar.gz", hash = "sha256:daec15a393cafc32b846b7ef93f9c952a1708863e242341ab5bde2e4beabb54e", size = 330538, upload-time = "2025-11-06T20:10:15.067Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/c2/7b/8e1d580c5178f0736b806b7199827e61e2a2569eec5b49ec75da6273bbdf/safety-3.8.1.tar.gz", hash = "sha256:e646123b976bbb6707cfaacae8c926e2f886b744a60e0f410e8610a3a4eaf7be", size = 412947, upload-time = "2026-05-29T15:09:33.355Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/55/c4b2058ca346e58124ba082a3596e30dc1f5793710f8173156c7c2d77048/safety-3.7.0-py3-none-any.whl", hash = "sha256:65e71db45eb832e8840e3456333d44c23927423753d5610596a09e909a66d2bf", size = 312436, upload-time = "2025-11-06T20:10:13.576Z" },
|
{ url = "https://files.pythonhosted.org/packages/4b/f5/498db84333a644835e572c0d96cfa705bf9871f863289a7845082d54755e/safety-3.8.1-py3-none-any.whl", hash = "sha256:953c1c3c60c873f53a6cc250b2a9c4b38bb6ef45f0625990e43f20bff916c965", size = 340521, upload-time = "2026-05-29T15:09:31.622Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1461,28 +1550,23 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "2.0.49"
|
version = "2.0.52"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" },
|
{ url = "https://files.pythonhosted.org/packages/d5/f5/71cb30af58c9b80a4e1fac0b73bb48f86d497a774a6a2eb6d2f1e657bb73/sqlalchemy-2.0.52-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:410d52be41d17f1a236d19520fbe776257dc16516ed06bd16d433311842aefd9", size = 2169537, upload-time = "2026-08-11T20:58:13.855Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/93/d07ebd645d1b07b6b5ed63450a70f063a346a7e0f2c8810daf2e532400cb/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfe9ce533dbe4d0a2ae1486546619bd30b76bcd670539a44d910361376175f5e", size = 3319606, upload-time = "2026-08-11T21:02:45.829Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/5c/290c84c7c2566ecd3b65baaae0fddec9bc33b033b398a06123bb86fbfc6e/sqlalchemy-2.0.52-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:812bae5138bfc0aa46fb0686da0fc7f581f68e2bbb05bc24c3713bebaedd1437", size = 3323642, upload-time = "2026-08-11T21:17:05.675Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" },
|
{ url = "https://files.pythonhosted.org/packages/13/f5/2cc160590ca49173359557880b92a0572293ccb899e8f6cedf150c5a3ddf/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:50bff43b632a56fbf5ed9afdd76307e1512b62051bcd5afb341ae67205bbb6c8", size = 3268125, upload-time = "2026-08-11T21:02:47.649Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/f3/ea8933fc9f7d1353e9c2ff9965eae687c4cef181120574591ed2fa0633e1/sqlalchemy-2.0.52-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:49565daf5af554f538e23aef1fc81a95a4e49658f152285e45c02f5fc44f04cd", size = 3289516, upload-time = "2026-08-11T21:17:07.267Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" },
|
{ url = "https://files.pythonhosted.org/packages/45/67/05cf86541c1e1716fca1e4a996954a439cd74501707cda607fb7cb02ef50/sqlalchemy-2.0.52-cp314-cp314-win32.whl", hash = "sha256:ab9da41e61b9979b910499d633b241df20c51ee5037e5405b11c2faac3cbe1a2", size = 2130249, upload-time = "2026-08-11T21:14:57.273Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/d7/8ac6ffa1e36169e762ef65bd835046abb2251b1bc17f8f6708e14ed8d31f/sqlalchemy-2.0.52-cp314-cp314-win_amd64.whl", hash = "sha256:a593db51b3bae75db17a5738ad5f992244b3a03863f83c28117ee482c6a3f76d", size = 2156718, upload-time = "2026-08-11T21:14:58.667Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" },
|
{ url = "https://files.pythonhosted.org/packages/dc/4b/e01a737eef378e734cc6394a82248a6ce13b167dfa36c731075ce9fc9c64/sqlalchemy-2.0.52-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1e61d08bdf4ee2f41024569e3400de7d6734ba498144766b11260936ccfa582", size = 2190344, upload-time = "2026-08-11T19:53:21.393Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" },
|
{ url = "https://files.pythonhosted.org/packages/b3/3f/3582293d1e185e71d19d7c731c3e2ee20ba21981c4a1115c0806c1f62120/sqlalchemy-2.0.52-py3-none-any.whl", hash = "sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89", size = 1950700, upload-time = "2026-08-11T20:47:21.603Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1536,6 +1620,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "truststore"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typer"
|
name = "typer"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
@@ -1553,11 +1646,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-openpyxl"
|
name = "types-openpyxl"
|
||||||
version = "3.1.5.20260408"
|
version = "3.1.5.20260807"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/74/c9/24f03f9d9fedd164de699c1418869bef9b819f59f75e7f647f5788c02d98/types_openpyxl-3.1.5.20260408.tar.gz", hash = "sha256:b49274d086fbb6e6bcd2a67d161dd1161d4d380488e4cce546d647d45eadcac2", size = 101361, upload-time = "2026-04-08T04:30:37.809Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/cd/89/e81814aac1c6ec46ee0006b723254ae737faa68c1ac80a7c3b81b3aa9f22/types_openpyxl-3.1.5.20260807.tar.gz", hash = "sha256:1a0a42b125f8023d3ae83cc057e379d301a87f45e60b6160917824fef28ab015", size = 101740, upload-time = "2026-08-07T04:17:25.557Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/e8/64db0c32c6fda4b198aff84329ceeea00a93d16c28f05e9fe0404ddb8b8c/types_openpyxl-3.1.5.20260408-py3-none-any.whl", hash = "sha256:7ab7586796aed017cde50b81bd67ba024120c39b99c102320b57dd91390c317f", size = 166044, upload-time = "2026-04-08T04:30:36.449Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/50/c7faba803c8a2e822ccc43a091c979cdbdc28e792e1eb0fb5ff172c81ee5/types_openpyxl-3.1.5.20260807-py3-none-any.whl", hash = "sha256:e64e9342cdac8a2d7b09f992d3606c532b75da43874f8107b6b5a122dc9d5681", size = 165826, upload-time = "2026-08-07T04:17:24.341Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1601,20 +1694,19 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.46.0"
|
version = "0.52.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "h11" },
|
{ name = "h11" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" },
|
{ url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
standard = [
|
standard = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
||||||
{ name = "httptools" },
|
{ name = "httptools" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
|
|||||||
+24
-23
@@ -14,17 +14,17 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^21.1.5",
|
"@angular/animations": "^22.1.2",
|
||||||
"@angular/cdk": "^21.1.5",
|
"@angular/cdk": "^22.1.2",
|
||||||
"@angular/common": "^21.1.5",
|
"@angular/common": "^22.1.2",
|
||||||
"@angular/compiler": "^21.1.5",
|
"@angular/compiler": "^22.1.2",
|
||||||
"@angular/core": "^21.1.5",
|
"@angular/core": "^22.1.2",
|
||||||
"@angular/forms": "^21.1.5",
|
"@angular/forms": "^22.1.2",
|
||||||
"@angular/material": "^21.1.5",
|
"@angular/material": "^22.1.2",
|
||||||
"@angular/material-moment-adapter": "^21.1.5",
|
"@angular/material-moment-adapter": "^22.1.2",
|
||||||
"@angular/platform-browser": "^21.1.5",
|
"@angular/platform-browser": "^22.1.2",
|
||||||
"@angular/platform-browser-dynamic": "^21.1.5",
|
"@angular/platform-browser-dynamic": "^22.1.2",
|
||||||
"@angular/router": "^21.1.5",
|
"@angular/router": "^22.1.2",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jspdf": "^4.2.0",
|
"jspdf": "^4.2.0",
|
||||||
"mathjs": "^15.1.1",
|
"mathjs": "^15.1.1",
|
||||||
@@ -33,22 +33,23 @@
|
|||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/build": "^21.1.4",
|
"@angular/build": "^22.1.4",
|
||||||
"@angular/cli": "^21.1.4",
|
"@angular/cli": "^22.1.4",
|
||||||
"@angular/compiler-cli": "^21.1.5",
|
"@angular/compiler-cli": "^22.1.2",
|
||||||
"@angular/language-service": "^21.1.5",
|
"@angular/language-service": "^22.1.2",
|
||||||
"angular-eslint": "^21.2.0",
|
"@eslint/js": "10.0.1",
|
||||||
"eslint": "^9.28.0",
|
"angular-eslint": "^22.1.0",
|
||||||
|
"eslint": "^10.8.1",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-perfectionist": "^4.15.0",
|
"eslint-plugin-perfectionist": "^5.10.1",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^17.3.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.9.6",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~6.0.3",
|
||||||
"typescript-eslint": "^8.33.1"
|
"typescript-eslint": "^8.67.0"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
<h2 class="row-container space-between wrapped">
|
<h2 class="row-container space-between wrapped">
|
||||||
<span>Account</span>
|
<span>Account</span>
|
||||||
<mat-icon matSuffix (click)="item.isStarred = !item.isStarred" class="pointer" [class.gold]="item.isStarred">
|
<mat-icon matSuffix (click)="item().isStarred = !item().isStarred" class="pointer" [class.gold]="item().isStarred">
|
||||||
{{ item.isStarred ? 'star' : 'star_border' }}
|
{{ item().isStarred ? 'star' : 'star_border' }}
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</h2>
|
</h2>
|
||||||
<form [formGroup]="form" class="flex-col wrapped">
|
@if (itemResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="card" [count]="1" />
|
||||||
|
<app-skeleton-loader type="line" [count]="10" />
|
||||||
|
} @else if (itemResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="itemResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<form class="flex-col wrapped" [formRoot]="accountForm">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Code</mat-label>
|
<mat-label>Code</mat-label>
|
||||||
<input matInput formControlName="code" />
|
<input matInput [formField]="accountForm.code" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input matInput #nameElement formControlName="name" />
|
<input matInput [formField]="accountForm.name" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Account Type</mat-label>
|
<mat-label>Account Type</mat-label>
|
||||||
<mat-select formControlName="type">
|
<mat-select [formField]="accountForm.type">
|
||||||
@for (at of accountTypes; track at) {
|
@for (at of accountTypes(); track at) {
|
||||||
<mat-option [value]="at.id">
|
<mat-option [value]="at.id">
|
||||||
{{ at.name }}
|
{{ at.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@@ -30,14 +36,14 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-checkbox formControlName="isActive" class="flex-auto">Is Active?</mat-checkbox>
|
<mat-checkbox [formField]="accountForm.isActive" class="flex-auto">Is Active?</mat-checkbox>
|
||||||
<mat-checkbox formControlName="isReconcilable" class="flex-auto">Is Reconcilable?</mat-checkbox>
|
<mat-checkbox [formField]="accountForm.isReconcilable" class="flex-auto">Is Reconcilable?</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Cost Centre</mat-label>
|
<mat-label>Cost Centre</mat-label>
|
||||||
<mat-select formControlName="costCentre">
|
<mat-select [formField]="accountForm.costCentre">
|
||||||
@for (cs of costCentres; track cs) {
|
@for (cs of costCentres(); track cs) {
|
||||||
<mat-option [value]="cs.id">
|
<mat-option [value]="cs.id">
|
||||||
{{ cs.name }}
|
{{ cs.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@@ -45,10 +51,11 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<button mat-raised-button color="primary" (click)="save()" class="">Save</button>
|
<button mat-raised-button color="primary" (click)="save()" class="">Save</button>
|
||||||
@if (!!item.id) {
|
@if (!!item().id) {
|
||||||
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, inject, afterNextRender, linkedSignal, input, computed } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatOptionModule } from '@angular/material/core';
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
@@ -9,13 +9,24 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Account } from '../../core/account';
|
import { Account } from '../../core/account';
|
||||||
import { AccountType } from '../../core/account-type';
|
|
||||||
import { AccountService } from '../../core/account.service';
|
import { AccountService } from '../../core/account.service';
|
||||||
import { CostCentre } from '../../core/cost-centre';
|
import { CostCentreService } from '../../cost-centre/cost-centre.service';
|
||||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
|
||||||
|
import { AccountTypeService } from '../account-type.service';
|
||||||
|
|
||||||
|
export interface AccountDetailFormData {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
type: number;
|
||||||
|
isActive: boolean;
|
||||||
|
isReconcilable: boolean;
|
||||||
|
costCentre: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-detail',
|
selector: 'app-account-detail',
|
||||||
@@ -23,79 +34,59 @@ import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dial
|
|||||||
styleUrls: ['./account-detail.component.css'],
|
styleUrls: ['./account-detail.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
ReactiveFormsModule,
|
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatOptionModule,
|
MatOptionModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
FormField,
|
||||||
|
FormRoot,
|
||||||
|
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AccountDetailComponent implements OnInit, AfterViewInit {
|
export class AccountDetailComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
private ser = inject(AccountService);
|
private ser = inject(AccountService);
|
||||||
|
private accountTypeService = inject(AccountTypeService);
|
||||||
|
private costCentreService = inject(CostCentreService);
|
||||||
|
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
|
||||||
@ViewChild('nameElement', { static: true }) nameElement!: ElementRef<HTMLInputElement>;
|
itemResource = this.ser.get(this.id);
|
||||||
form: FormGroup<{
|
|
||||||
code: FormControl<number | string>;
|
|
||||||
name: FormControl<string | null>;
|
|
||||||
type: FormControl<number>;
|
|
||||||
isActive: FormControl<boolean>;
|
|
||||||
isReconcilable: FormControl<boolean>;
|
|
||||||
costCentre: FormControl<string | null>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
accountTypes: AccountType[] = [];
|
item = computed(() => this.itemResource.value() ?? new Account());
|
||||||
costCentres: CostCentre[] = [];
|
|
||||||
item: Account = new Account();
|
accountModel = linkedSignal({
|
||||||
|
source: this.item,
|
||||||
|
computation: (itemVal): AccountDetailFormData => {
|
||||||
|
return {
|
||||||
|
code: (itemVal.code || '(Auto)').toString(),
|
||||||
|
name: itemVal.name || '',
|
||||||
|
type: itemVal.type ?? 0,
|
||||||
|
isActive: itemVal.isActive ?? true,
|
||||||
|
isReconcilable: itemVal.isReconcilable ?? false,
|
||||||
|
costCentre: itemVal.costCentre?.id ?? '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
accountForm = form(this.accountModel);
|
||||||
|
|
||||||
|
accountTypesResource = this.accountTypeService.list();
|
||||||
|
costCentresResource = this.costCentreService.list();
|
||||||
|
accountTypes = computed(() => this.accountTypesResource.value() || []);
|
||||||
|
costCentres = computed(() => this.costCentresResource.value() || []);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
code: new FormControl<string | number>({ value: 0, disabled: true }, { nonNullable: true }),
|
this.accountForm.name().focusBoundControl();
|
||||||
name: new FormControl<string | null>(null),
|
|
||||||
type: new FormControl<number>(0, { nonNullable: true }),
|
|
||||||
isActive: new FormControl<boolean>(true, { nonNullable: true }),
|
|
||||||
isReconcilable: new FormControl<boolean>(false, { nonNullable: true }),
|
|
||||||
costCentre: new FormControl<string | null>(null),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as {
|
|
||||||
item: Account;
|
|
||||||
accountTypes: AccountType[];
|
|
||||||
costCentres: CostCentre[];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.accountTypes = data.accountTypes;
|
|
||||||
this.costCentres = data.costCentres;
|
|
||||||
this.showItem(data.item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showItem(item: Account) {
|
|
||||||
this.item = item;
|
|
||||||
this.form.setValue({
|
|
||||||
code: this.item.code || '(Auto)',
|
|
||||||
name: this.item.name || '',
|
|
||||||
type: this.item.type,
|
|
||||||
isActive: this.item.isActive,
|
|
||||||
isReconcilable: this.item.isReconcilable,
|
|
||||||
costCentre: this.item.costCentre.id ?? '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.nameElement.nativeElement.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.ser.saveOrUpdate(this.getItem()).subscribe({
|
this.ser.saveOrUpdate(this.getItem()).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@@ -103,19 +94,19 @@ export class AccountDetailComponent implements OnInit, AfterViewInit {
|
|||||||
this.router.navigateByUrl('/accounts');
|
this.router.navigateByUrl('/accounts');
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.snackBar.open(error, 'Danger');
|
this.snackBar.open(error as string, 'Danger');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
this.ser.delete(this.item.id as string).subscribe({
|
this.ser.delete(this.item().id as string).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('', 'Success');
|
this.snackBar.open('', 'Success');
|
||||||
this.router.navigateByUrl('/accounts');
|
this.router.navigateByUrl('/accounts');
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.snackBar.open(error, 'Danger');
|
this.snackBar.open(error as string, 'Danger');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,12 +125,13 @@ export class AccountDetailComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getItem(): Account {
|
getItem(): Account {
|
||||||
const formModel = this.form.value;
|
const formModel = this.accountModel();
|
||||||
this.item.name = formModel.name ?? '';
|
const item = this.item();
|
||||||
this.item.type = formModel.type ?? 0;
|
item.name = formModel.name ?? '';
|
||||||
this.item.isActive = formModel.isActive ?? true;
|
item.type = formModel.type ?? 0;
|
||||||
this.item.isReconcilable = formModel.isReconcilable ?? false;
|
item.isActive = formModel.isActive ?? true;
|
||||||
this.item.costCentre.id = formModel.costCentre ?? '';
|
item.isReconcilable = formModel.isReconcilable ?? false;
|
||||||
return this.item;
|
item.costCentre.id = formModel.costCentre ?? '';
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Account } from '../core/account';
|
|
||||||
import { accountListResolver } from './account-list.resolver';
|
|
||||||
|
|
||||||
describe('accountListResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<Account[]> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => accountListResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Account } from '../core/account';
|
|
||||||
import { AccountService } from '../core/account.service';
|
|
||||||
|
|
||||||
export const accountListResolver: ResolveFn<Account[]> = () => {
|
|
||||||
return inject(AccountService).list();
|
|
||||||
};
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
||||||
import { MatSort, Sort } from '@angular/material/sort';
|
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { map, tap } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Account } from '../../core/account';
|
|
||||||
|
|
||||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
|
||||||
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
|
||||||
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
|
||||||
export class AccountListDataSource extends DataSource<Account> {
|
|
||||||
private filterValue = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public data: Account[],
|
|
||||||
private filter: Observable<string>,
|
|
||||||
private paginator?: MatPaginator,
|
|
||||||
private sort?: MatSort,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.filter = filter.pipe(
|
|
||||||
tap((x) => {
|
|
||||||
this.filterValue = x;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<Account[]> {
|
|
||||||
const dataMutations: (EventEmitter<PageEvent> | EventEmitter<Sort>)[] = [];
|
|
||||||
if (this.paginator) {
|
|
||||||
dataMutations.push((this.paginator as MatPaginator).page);
|
|
||||||
}
|
|
||||||
if (this.sort) {
|
|
||||||
dataMutations.push((this.sort as MatSort).sortChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return merge(observableOf(this.data), this.filter, ...dataMutations)
|
|
||||||
.pipe(
|
|
||||||
map(() => this.getFilteredData([...this.data])),
|
|
||||||
tap((x: Account[]) => {
|
|
||||||
if (this.paginator) {
|
|
||||||
this.paginator.length = x.length;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(map((x: Account[]) => this.getPagedData(this.getSortedData(x))));
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
|
|
||||||
private getFilteredData(data: Account[]): Account[] {
|
|
||||||
return this.filterValue.split(' ').reduce(
|
|
||||||
(p: Account[], c: string) =>
|
|
||||||
p.filter((x) => {
|
|
||||||
const accountString = `${x.name} ${x.type} ${x.costCentre}${
|
|
||||||
x.isActive ? ' active' : ' inactive'
|
|
||||||
}`.toLowerCase();
|
|
||||||
return accountString.indexOf(c) !== -1;
|
|
||||||
}),
|
|
||||||
Object.assign([], data),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPagedData(data: Account[]) {
|
|
||||||
if (this.paginator === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
|
||||||
return data.splice(startIndex, this.paginator.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSortedData(data: Account[]) {
|
|
||||||
if (this.sort === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (!this.sort.active || this.sort.direction === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sort = this.sort as MatSort;
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
const isAsc = sort.direction === 'asc';
|
|
||||||
switch (sort.active) {
|
|
||||||
case 'name':
|
|
||||||
return compare(a.name, b.name, isAsc);
|
|
||||||
case 'type':
|
|
||||||
return compare(a.typeName, b.typeName, isAsc);
|
|
||||||
case 'isActive':
|
|
||||||
return compare(a.isActive, b.isActive, isAsc);
|
|
||||||
case 'isReconcilable':
|
|
||||||
return compare(a.isReconcilable, b.isReconcilable, isAsc);
|
|
||||||
case 'costCentre':
|
|
||||||
return compare(a.costCentre.name, b.costCentre.name, isAsc);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,15 +6,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="accountForm">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Filter</mat-label>
|
<mat-label>Filter</mat-label>
|
||||||
<input type="text" matInput #filterElement formControlName="filter" autocomplete="off" />
|
<input type="text" matInput #filterElement [formField]="accountForm.filter" autocomplete="off" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
@if (listResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (listResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="listResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
|
||||||
<!-- Name Column -->
|
<!-- Name Column -->
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||||
@@ -54,13 +59,14 @@
|
|||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<mat-paginator
|
<mat-paginator
|
||||||
#paginator
|
(page)="handlePageEvent($event)"
|
||||||
[length]="dataSource.data.length"
|
[length]="filteredList().length"
|
||||||
[pageIndex]="0"
|
[pageIndex]="pageIndex()"
|
||||||
[pageSize]="50"
|
[pageSize]="pageSize()"
|
||||||
[pageSizeOptions]="[25, 50, 100, 250]"
|
[pageSizeOptions]="[25, 50, 100, 250]"
|
||||||
>
|
>
|
||||||
</mat-paginator>
|
</mat-paginator>
|
||||||
|
}
|
||||||
@@ -1,77 +1,174 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
|
import {
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
Component,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
effect,
|
||||||
|
computed,
|
||||||
|
debounced,
|
||||||
|
input,
|
||||||
|
linkedSignal,
|
||||||
|
afterNextRender,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { form, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Account } from '../../core/account';
|
import { Account } from '../../core/account';
|
||||||
import { AccountType } from '../../core/account-type';
|
import { AccountService } from '../../core/account.service';
|
||||||
import { AccountListDataSource } from './account-list-datasource';
|
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
|
||||||
|
import { AccountTypeService } from '../account-type.service';
|
||||||
|
|
||||||
|
export interface AccountListFormData {
|
||||||
|
filter: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-list',
|
selector: 'app-account-list',
|
||||||
templateUrl: './account-list.component.html',
|
templateUrl: './account-list.component.html',
|
||||||
styleUrls: ['./account-list.component.css'],
|
styleUrls: ['./account-list.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule,
|
RouterLink,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
ReactiveFormsModule,
|
FormField,
|
||||||
|
FormRoot,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
|
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AccountListComponent implements OnInit, AfterViewInit {
|
export class AccountListComponent {
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private ser = inject(AccountService);
|
||||||
|
private accountTypeSer = inject(AccountTypeService);
|
||||||
|
pageSize = signal(50);
|
||||||
|
pageIndex = signal(0);
|
||||||
|
sortActive = signal('');
|
||||||
|
sortDirection = signal('');
|
||||||
|
q = input('', { transform: (v: string | null | undefined) => v ?? '' });
|
||||||
|
formModel = linkedSignal({
|
||||||
|
source: this.q,
|
||||||
|
computation: (qValue): AccountListFormData => ({ filter: qValue }),
|
||||||
|
});
|
||||||
|
|
||||||
@ViewChild('filterElement', { static: true }) filterElement!: ElementRef<HTMLInputElement>;
|
accountForm = form(this.formModel);
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
|
||||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
|
||||||
dataSource: AccountListDataSource;
|
|
||||||
filter: Observable<string>;
|
|
||||||
form: FormGroup<{
|
|
||||||
filter: FormControl<string>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
list: Account[] = [];
|
filterSignal = computed(() => this.formModel().filter);
|
||||||
accountTypes: AccountType[] = [];
|
|
||||||
|
debouncedFilter = debounced(this.filterSignal, 150);
|
||||||
|
|
||||||
|
listResource = this.ser.list();
|
||||||
|
accountTypesResource = this.accountTypeSer.list();
|
||||||
|
|
||||||
|
accountTypes = computed(() => this.accountTypesResource.value() ?? []);
|
||||||
|
list = computed(() => this.listResource.value() ?? []);
|
||||||
|
listWithTypes = computed(() => {
|
||||||
|
const data = this.list();
|
||||||
|
const types = this.accountTypes();
|
||||||
|
|
||||||
|
return data.map(
|
||||||
|
(account) =>
|
||||||
|
new Account({
|
||||||
|
...account,
|
||||||
|
typeName: types.find((t) => t.id === account.type)?.name ?? account.type.toString(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredList = computed(() => {
|
||||||
|
const q = (this.debouncedFilter.value() ?? '').trim().toLowerCase();
|
||||||
|
const data = this.listWithTypes();
|
||||||
|
if (!q) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return q.split(' ').reduce(
|
||||||
|
(p: Account[], c: string) =>
|
||||||
|
p.filter((x) => {
|
||||||
|
const accountString = `${x.name} ${x.type} ${x.costCentre}${
|
||||||
|
x.isActive ? ' active' : ' inactive'
|
||||||
|
}`.toLowerCase();
|
||||||
|
return accountString.indexOf(c) !== -1;
|
||||||
|
}),
|
||||||
|
[...data],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedList = computed(() => {
|
||||||
|
const data = this.filteredList();
|
||||||
|
const active = this.sortActive();
|
||||||
|
const direction = this.sortDirection();
|
||||||
|
|
||||||
|
if (!active || direction === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const isAsc = direction === 'asc';
|
||||||
|
switch (active) {
|
||||||
|
case 'name':
|
||||||
|
return compare(a.name, b.name, isAsc);
|
||||||
|
case 'type':
|
||||||
|
return compare(a.typeName, b.typeName, isAsc);
|
||||||
|
case 'isActive':
|
||||||
|
return compare(a.isActive, b.isActive, isAsc);
|
||||||
|
case 'isReconcilable':
|
||||||
|
return compare(a.isReconcilable, b.isReconcilable, isAsc);
|
||||||
|
case 'costCentre':
|
||||||
|
return compare(a.costCentre.name, b.costCentre.name, isAsc);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource = computed(() => {
|
||||||
|
const data = this.sortedList();
|
||||||
|
const pageIndex = this.pageIndex();
|
||||||
|
const pageSize = this.pageSize();
|
||||||
|
return data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
displayedColumns = ['name', 'type', 'isActive', 'isReconcilable', 'costCentre'];
|
displayedColumns = ['name', 'type', 'isActive', 'isReconcilable', 'costCentre'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
filter: new FormControl<string>('', { nonNullable: true }),
|
this.accountForm().focusBoundControl();
|
||||||
|
});
|
||||||
|
effect(() => {
|
||||||
|
const q = this.debouncedFilter.value() ?? '';
|
||||||
|
this.router.navigate([], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: { q: q || null },
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
replaceUrl: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// Listen to Filter Change
|
|
||||||
this.filter = this.form.controls.filter.valueChanges.pipe(debounceTime(150), distinctUntilChanged());
|
|
||||||
|
|
||||||
this.dataSource = new AccountListDataSource(this.list, this.filter, this.paginator, this.sort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
handlePageEvent(e: PageEvent) {
|
||||||
this.route.data.subscribe((value) => {
|
this.pageSize.set(e.pageSize);
|
||||||
const data = value as { list: Account[]; accountTypes: AccountType[] };
|
this.pageIndex.set(e.pageIndex);
|
||||||
this.accountTypes = data.accountTypes;
|
|
||||||
data.list.forEach((x) => (x.typeName = this.accountTypes.find((y) => y.id === x.type)?.name ?? ''));
|
|
||||||
this.list = data.list;
|
|
||||||
});
|
|
||||||
this.dataSource = new AccountListDataSource(this.list, this.filter, this.paginator, this.sort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
sortData(sort: Sort) {
|
||||||
setTimeout(() => {
|
this.sortActive.set(sort.active);
|
||||||
this.filterElement.nativeElement.focus();
|
this.sortDirection.set(sort.direction);
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
||||||
|
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { AccountType } from '../core/account-type';
|
|
||||||
import { accountTypeResolver } from './account-type.resolver';
|
|
||||||
|
|
||||||
describe('accountTypeResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<AccountType[]> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => accountTypeResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { AccountType } from '../core/account-type';
|
|
||||||
import { AccountTypeService } from './account-type.service';
|
|
||||||
|
|
||||||
export const accountTypeResolver: ResolveFn<AccountType[]> = () => {
|
|
||||||
return inject(AccountTypeService).list();
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { AccountType } from '../core/account-type';
|
import { AccountType } from '../core/account-type';
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||||
|
|
||||||
const url = '/api/account-types';
|
const url = '/api/account-types';
|
||||||
const serviceName = 'AccountTypeService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -16,9 +12,7 @@ export class AccountTypeService {
|
|||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
list(): Observable<AccountType[]> {
|
list() {
|
||||||
return this.http.get<AccountType[]>(url).pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<
|
return httpResource<AccountType[]>(() => url);
|
||||||
AccountType[]
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Account } from '../core/account';
|
|
||||||
import { accountResolver } from './account.resolver';
|
|
||||||
|
|
||||||
describe('accountResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<Account> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => accountResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Account } from '../core/account';
|
|
||||||
import { AccountService } from '../core/account.service';
|
|
||||||
|
|
||||||
export const accountResolver: ResolveFn<Account> = (route) => {
|
|
||||||
const id = route.paramMap.get('id');
|
|
||||||
return inject(AccountService).get(id);
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { costCentreListResolver } from '../cost-centre/cost-centre-list.resolver';
|
|
||||||
import { AccountDetailComponent } from './account-detail/account-detail.component';
|
import { AccountDetailComponent } from './account-detail/account-detail.component';
|
||||||
import { accountListResolver } from './account-list.resolver';
|
|
||||||
import { AccountListComponent } from './account-list/account-list.component';
|
import { AccountListComponent } from './account-list/account-list.component';
|
||||||
import { accountTypeResolver } from './account-type.resolver';
|
|
||||||
import { accountResolver } from './account.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -16,10 +12,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Accounts',
|
permission: 'Accounts',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
list: accountListResolver,
|
|
||||||
accountTypes: accountTypeResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'new',
|
path: 'new',
|
||||||
@@ -28,11 +20,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Accounts',
|
permission: 'Accounts',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
item: accountResolver,
|
|
||||||
accountTypes: accountTypeResolver,
|
|
||||||
costCentres: costCentreListResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
@@ -41,10 +28,5 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Accounts',
|
permission: 'Accounts',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
item: accountResolver,
|
|
||||||
accountTypes: accountTypeResolver,
|
|
||||||
costCentres: costCentreListResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,60 +1,44 @@
|
|||||||
import { LayoutModule } from '@angular/cdk/layout';
|
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||||
import { provideHttpClient, withInterceptors, withFetch } from '@angular/common/http';
|
import {
|
||||||
import { ApplicationConfig, importProvidersFrom, LOCALE_ID, provideZonelessChangeDetection } from '@angular/core';
|
ApplicationConfig,
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
importProvidersFrom,
|
||||||
|
isDevMode,
|
||||||
|
LOCALE_ID,
|
||||||
|
provideZonelessChangeDetection,
|
||||||
|
} from '@angular/core';
|
||||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
|
||||||
import { MatListModule } from '@angular/material/list';
|
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
||||||
import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
|
||||||
import { MatTableModule } from '@angular/material/table';
|
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { provideRouter, withRouterConfig } from '@angular/router';
|
import { provideRouter, withComponentInputBinding, withRouterConfig } from '@angular/router';
|
||||||
|
|
||||||
import { dateFormat } from './app.environment';
|
import { dateFormat } from './app.environment';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { AuthService } from './auth/auth.service';
|
import { AuthService } from './auth/auth.service';
|
||||||
import { authInterceptor } from './core/auth.interceptor';
|
import { authInterceptor } from './core/auth.interceptor';
|
||||||
|
import { delayInterceptor } from './core/delay-interceptor';
|
||||||
import { jwtInterceptor } from './core/jwt.interceptor';
|
import { jwtInterceptor } from './core/jwt.interceptor';
|
||||||
import { refreshInterceptor } from './core/refresh.interceptor';
|
import { refreshInterceptor } from './core/refresh.interceptor';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
importProvidersFrom(
|
importProvidersFrom(BrowserModule),
|
||||||
BrowserModule,
|
|
||||||
LayoutModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatDividerModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatSidenavModule,
|
|
||||||
MatToolbarModule,
|
|
||||||
MatListModule,
|
|
||||||
MatMenuModule,
|
|
||||||
MatProgressSpinnerModule,
|
|
||||||
MatSnackBarModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatPaginatorModule,
|
|
||||||
MatSortModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
),
|
|
||||||
{ provide: LOCALE_ID, useValue: 'en-IN' },
|
{ provide: LOCALE_ID, useValue: 'en-IN' },
|
||||||
AuthService,
|
AuthService,
|
||||||
provideHttpClient(withFetch(), withInterceptors([refreshInterceptor, jwtInterceptor, authInterceptor])),
|
provideHttpClient(
|
||||||
|
withInterceptors([
|
||||||
|
authInterceptor,
|
||||||
|
jwtInterceptor,
|
||||||
|
refreshInterceptor,
|
||||||
|
...(isDevMode() ? [delayInterceptor] : []),
|
||||||
|
]),
|
||||||
|
),
|
||||||
provideRouter(
|
provideRouter(
|
||||||
routes,
|
routes,
|
||||||
withRouterConfig({
|
withRouterConfig({
|
||||||
onSameUrlNavigation: 'reload',
|
onSameUrlNavigation: 'reload',
|
||||||
}),
|
}),
|
||||||
|
withComponentInputBinding(),
|
||||||
),
|
),
|
||||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||||
{ provide: MAT_DATE_FORMATS, useValue: dateFormat },
|
{ provide: MAT_DATE_FORMATS, useValue: dateFormat },
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
|
|
||||||
import { AttendanceItem } from './attendance-item';
|
|
||||||
|
|
||||||
export class AttendanceDataSource extends DataSource<AttendanceItem> {
|
|
||||||
constructor(private data: Observable<AttendanceItem[]>) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<AttendanceItem[]> {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { AttendanceType } from './attendance-type';
|
|
||||||
import { AttendanceTypeService } from './attendance-type.service';
|
|
||||||
|
|
||||||
export const attendanceTypeResolver: ResolveFn<AttendanceType[]> = () => {
|
|
||||||
return inject(AttendanceTypeService).list();
|
|
||||||
};
|
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||||
import { AttendanceType } from './attendance-type';
|
import { AttendanceType } from './attendance-type';
|
||||||
|
|
||||||
const url = '/api/attendance-types';
|
const url = '/api/attendance-types';
|
||||||
const serviceName = 'AttendanceTypeService';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttendanceTypeService {
|
export class AttendanceTypeService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
list(): Observable<AttendanceType[]> {
|
list() {
|
||||||
return this.http
|
return httpResource<AttendanceType[]>(() => url);
|
||||||
.get<AttendanceType[]>(url)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<AttendanceType[]>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<h2>Attendance</h2>
|
<h2>Attendance</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto basis-4-5">
|
<mat-form-field class="flex-auto basis-4-5">
|
||||||
<mat-label>Date</mat-label>
|
<mat-label>Date</mat-label>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#dateElement
|
#dateElement
|
||||||
[matDatepicker]="date"
|
[matDatepicker]="date"
|
||||||
(focus)="dateElement.select()"
|
(focus)="dateElement.select()"
|
||||||
formControlName="date"
|
[formField]="form.date"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="date"></mat-datepicker-toggle>
|
||||||
@@ -17,7 +17,12 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
||||||
</div>
|
</div>
|
||||||
<mat-table #table [dataSource]="dataSource" formArrayName="attendances">
|
@if (infoResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (infoResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="infoResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="info().body">
|
||||||
<!-- Code Column -->
|
<!-- Code Column -->
|
||||||
<ng-container matColumnDef="code">
|
<ng-container matColumnDef="code">
|
||||||
<mat-header-cell *matHeaderCellDef class="right">Code</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef class="right">Code</mat-header-cell>
|
||||||
@@ -45,14 +50,16 @@
|
|||||||
<!-- Status Column -->
|
<!-- Status Column -->
|
||||||
<ng-container matColumnDef="status">
|
<ng-container matColumnDef="status">
|
||||||
<mat-header-cell *matHeaderCellDef>Status</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>Status</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="flex-auto">
|
<mat-cell *matCellDef="let row; let i = index" class="flex-auto">
|
||||||
<mat-select formControlName="attendanceType" name="attendanceType">
|
@if (form.attendances[i]) {
|
||||||
@for (at of attendanceTypes; track at) {
|
<mat-select [formField]="form.attendances[i].attendanceType">
|
||||||
|
@for (at of attendanceTypes(); track at) {
|
||||||
<mat-option [value]="at.id">
|
<mat-option [value]="at.id">
|
||||||
{{ at.name }}
|
{{ at.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
}
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -61,7 +68,7 @@
|
|||||||
<mat-header-cell *matHeaderCellDef>Prints</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>Prints</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row; let i = index" class="no-bg">
|
<mat-cell *matCellDef="let row; let i = index" class="no-bg">
|
||||||
{{ row.prints }}
|
{{ row.prints }}
|
||||||
@if (!form.controls.attendances.controls[i].controls.attendanceType.pristine) {
|
@if (form.attendances[i] && form.attendances[i].attendanceType !== null) {
|
||||||
<mat-icon class="no-bg">new_releases</mat-icon>
|
<mat-icon class="no-bg">new_releases</mat-icon>
|
||||||
}
|
}
|
||||||
<mat-chip-set class="no-bg">
|
<mat-chip-set class="no-bg">
|
||||||
@@ -82,8 +89,9 @@
|
|||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; let i = index; columns: displayedColumns" [class]="getClass(i)"></mat-row>
|
<mat-row *matRowDef="let row; let i = index; columns: displayedColumns" [class]="getClass(i)"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<button mat-raised-button color="primary" (click)="save()" [disabled]="form.pristine">Save</button>
|
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, HostListener, inject, input, computed, linkedSignal, afterNextRender } from '@angular/core';
|
||||||
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatChipsModule } from '@angular/material/chips';
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
import { MatOptionModule } from '@angular/material/core';
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
@@ -11,23 +11,28 @@ import { MatInputModule } from '@angular/material/input';
|
|||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
|
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { Attendance } from './attendance';
|
import { Attendance } from './attendance';
|
||||||
import { AttendanceDataSource } from './attendance-datasource';
|
import { AttendanceTypeService } from './attendance-type.service';
|
||||||
import { AttendanceItem } from './attendance-item';
|
|
||||||
import { AttendanceType } from './attendance-type';
|
|
||||||
import { AttendanceService } from './attendance.service';
|
import { AttendanceService } from './attendance.service';
|
||||||
|
|
||||||
|
export interface AttendanceFormData {
|
||||||
|
date: moment.Moment;
|
||||||
|
attendances: { attendanceType: number | null }[];
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-attendance',
|
selector: 'app-attendance',
|
||||||
templateUrl: './attendance.component.html',
|
templateUrl: './attendance.component.html',
|
||||||
styleUrls: ['./attendance.component.css'],
|
styleUrls: ['./attendance.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
FormField,
|
||||||
|
FormRoot,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
@@ -37,76 +42,62 @@ import { AttendanceService } from './attendance.service';
|
|||||||
MatOptionModule,
|
MatOptionModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
|
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AttendanceComponent implements OnInit {
|
export class AttendanceComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
private auth = inject(AuthService);
|
private auth = inject(AuthService);
|
||||||
private ser = inject(AttendanceService);
|
private ser = inject(AttendanceService);
|
||||||
|
private attendanceTypeSer = inject(AttendanceTypeService);
|
||||||
|
date = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
|
||||||
@ViewChild('dateElement', { static: false }) dateElement!: ElementRef<HTMLInputElement>;
|
infoResource = this.ser.get(this.date);
|
||||||
public attendanceObservable = new BehaviorSubject<AttendanceItem[]>([]);
|
info = computed(() => this.infoResource.value() ?? new Attendance());
|
||||||
dataSource: AttendanceDataSource = new AttendanceDataSource(this.attendanceObservable);
|
attendanceTypesResource = this.attendanceTypeSer.list();
|
||||||
form: FormGroup<{
|
attendanceTypes = computed(() => this.attendanceTypesResource.value() ?? []);
|
||||||
date: FormControl<moment.Moment>;
|
|
||||||
attendances: FormArray<
|
|
||||||
FormGroup<{
|
|
||||||
attendanceType: FormControl<number>;
|
|
||||||
}>
|
|
||||||
>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
info: Attendance = new Attendance();
|
model = linkedSignal({
|
||||||
attendanceTypes: AttendanceType[] = [];
|
source: this.info,
|
||||||
|
computation: (infoVal): AttendanceFormData => ({
|
||||||
|
date: moment(infoVal.date),
|
||||||
|
attendances: infoVal.body.map((x) => ({
|
||||||
|
attendanceType: x.attendanceType.id,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.model);
|
||||||
|
|
||||||
displayedColumns = ['code', 'name', 'designation', 'department', 'status', 'prints'];
|
displayedColumns = ['code', 'name', 'designation', 'department', 'status', 'prints'];
|
||||||
|
|
||||||
@HostListener('window:keydown.f2', ['$event'])
|
@HostListener('window:keydown.f2', ['$event'])
|
||||||
focusDate(event: Event) {
|
focusDate(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.dateElement.nativeElement.focus();
|
this.form().focusBoundControl();
|
||||||
this.dateElement.nativeElement.select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
date: new FormControl(moment(new Date()), { nonNullable: true }),
|
this.form().focusBoundControl();
|
||||||
attendances: new FormArray<FormGroup<{ attendanceType: FormControl<number> }>>([]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as { info: Attendance; attendanceTypes: AttendanceType[] };
|
|
||||||
|
|
||||||
this.info = data.info;
|
|
||||||
this.attendanceTypes = data.attendanceTypes;
|
|
||||||
this.form.controls.date.setValue(moment(this.info.date, 'DD-MMM-YYYY'));
|
|
||||||
this.form.controls.attendances.clear();
|
|
||||||
this.info.body.forEach((x) =>
|
|
||||||
this.form.controls.attendances.push(
|
|
||||||
new FormGroup({
|
|
||||||
attendanceType: new FormControl(x.attendanceType.id, { nonNullable: true }),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.dataSource = new AttendanceDataSource(this.attendanceObservable);
|
|
||||||
this.attendanceObservable.next(this.info.body);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass(index: number) {
|
getClass(index: number) {
|
||||||
const array = this.form.controls.attendances;
|
const array = this.model().attendances;
|
||||||
const id = array.controls[index].value.attendanceType;
|
if (!array || !array[index]) return '';
|
||||||
const { name } = this.attendanceTypes.filter((x) => x.id === id)[0];
|
const id = array[index].attendanceType;
|
||||||
return name.toLowerCase().replace(/(\s+\+\s+)|(\s+)/g, '-');
|
const typeObj = this.attendanceTypes().filter((x) => x.id === id)[0];
|
||||||
|
if (!typeObj) return '';
|
||||||
|
return typeObj.name.toLowerCase().replace(/(\s+\+\s+)|(\s+)/g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
const date = moment(this.form.value.date).format('DD-MMM-YYYY');
|
const date = moment(this.model().date).format('DD-MMM-YYYY');
|
||||||
this.router.navigate(['/attendance', date]);
|
this.router.navigate(['/attendance', date]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,20 +107,23 @@ export class AttendanceComponent implements OnInit {
|
|||||||
this.snackBar.open('', 'Success');
|
this.snackBar.open('', 'Success');
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.snackBar.open(error, 'Danger');
|
this.snackBar.open(error as string, 'Danger');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttendance(): Attendance {
|
getAttendance(): Attendance {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
this.info.date = moment(formModel.date).format('DD-MMM-YYYY');
|
const currentInfo = this.info();
|
||||||
const array = this.form.controls.attendances;
|
currentInfo.date = moment(formModel.date).format('DD-MMM-YYYY');
|
||||||
|
const array = formModel.attendances;
|
||||||
if (array) {
|
if (array) {
|
||||||
this.info.body.forEach((item, index) => {
|
currentInfo.body.forEach((item, index) => {
|
||||||
item.attendanceType.id = array.controls[index].value.attendanceType ?? 0;
|
if (array[index]) {
|
||||||
|
item.attendanceType.id = array[index].attendanceType ?? 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.info;
|
return currentInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Attendance } from './attendance';
|
|
||||||
import { attendanceResolver } from './attendance.resolver';
|
|
||||||
|
|
||||||
describe('attendanceResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<Attendance> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => attendanceResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Attendance } from './attendance';
|
|
||||||
import { AttendanceService } from './attendance.service';
|
|
||||||
|
|
||||||
export const attendanceResolver: ResolveFn<Attendance> = (route) => {
|
|
||||||
const date = route.paramMap.get('date');
|
|
||||||
return inject(AttendanceService).get(date);
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { attendanceTypeResolver } from './attendance-type.resolver';
|
|
||||||
import { AttendanceComponent } from './attendance.component';
|
import { AttendanceComponent } from './attendance.component';
|
||||||
import { attendanceResolver } from './attendance.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -13,10 +11,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Attendance',
|
permission: 'Attendance',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: attendanceResolver,
|
|
||||||
attendanceTypes: attendanceTypeResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -26,10 +20,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Attendance',
|
permission: 'Attendance',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: attendanceResolver,
|
|
||||||
attendanceTypes: attendanceTypeResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -7,23 +7,21 @@ import { ErrorLoggerService } from '../core/error-logger.service';
|
|||||||
import { Attendance } from './attendance';
|
import { Attendance } from './attendance';
|
||||||
|
|
||||||
const url = '/api/attendance';
|
const url = '/api/attendance';
|
||||||
const serviceName = 'AttendanceService';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AttendanceService {
|
export class AttendanceService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
get(date: string | null): Observable<Attendance> {
|
get(date: Signal<string | null>) {
|
||||||
const getUrl: string = date === null ? url : `${url}/${date}`;
|
return httpResource<Attendance>(() => {
|
||||||
return this.http
|
const getUrl: string = date() === null ? url : `${url}/${date()}`;
|
||||||
.get<Attendance>(getUrl)
|
return getUrl;
|
||||||
.pipe(catchError(this.log.handleError(serviceName, `get date=${date}`))) as Observable<Attendance>;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
save(attendance: Attendance): Observable<Attendance> {
|
save(attendance: Attendance): Observable<Attendance> {
|
||||||
return this.http
|
return this.http
|
||||||
.post<Attendance>(`${url}/${attendance.date}`, attendance)
|
.post<Attendance>(`${url}/${attendance.date}`, attendance)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<Attendance>;
|
.pipe(catchError(this.log.handleError('AttendanceService', 'save'))) as Observable<Attendance>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Username</mat-label>
|
<mat-label>Username</mat-label>
|
||||||
<input matInput #nameElement formControlName="username" />
|
<input matInput [formField]="form.username" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Password</mat-label>
|
<mat-label>Password</mat-label>
|
||||||
<input matInput formControlName="password" [type]="hide ? 'password' : 'text'" (keyup.enter)="login()" />
|
<input matInput [formField]="form.password" [type]="hide ? 'password' : 'text'" (keyup.enter)="login()" />
|
||||||
<mat-icon matSuffix (click)="hide = !hide">{{ hide ? 'visibility' : 'visibility_off' }}</mat-icon>
|
<mat-icon matSuffix (click)="hide = !hide">{{ hide ? 'visibility' : 'visibility_off' }}</mat-icon>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Otp</mat-label>
|
<mat-label>Otp</mat-label>
|
||||||
<input matInput formControlName="otp" />
|
<input matInput [formField]="form.otp" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, inject, OnInit, afterNextRender, linkedSignal } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
@@ -11,13 +11,19 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { CookieService } from '../../shared/cookie.service';
|
import { CookieService } from '../../shared/cookie.service';
|
||||||
import { AuthService } from '../auth.service';
|
import { AuthService } from '../auth.service';
|
||||||
|
|
||||||
|
export interface LoginFormData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
otp: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrls: ['./login.component.css'],
|
styleUrls: ['./login.component.css'],
|
||||||
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDividerModule, MatButtonModule],
|
imports: [FormField, FormRoot, MatFormFieldModule, MatInputModule, MatIconModule, MatDividerModule, MatButtonModule],
|
||||||
})
|
})
|
||||||
export class LoginComponent implements OnInit, AfterViewInit {
|
export class LoginComponent implements OnInit {
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private auth = inject(AuthService);
|
private auth = inject(AuthService);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
@@ -25,12 +31,16 @@ export class LoginComponent implements OnInit, AfterViewInit {
|
|||||||
private cs = inject(CookieService);
|
private cs = inject(CookieService);
|
||||||
private cd = inject(ChangeDetectorRef);
|
private cd = inject(ChangeDetectorRef);
|
||||||
|
|
||||||
@ViewChild('nameElement', { static: true }) nameElement!: ElementRef<HTMLInputElement>;
|
model = linkedSignal({
|
||||||
form: FormGroup<{
|
source: () => null,
|
||||||
username: FormControl<string>;
|
computation: (): LoginFormData => ({
|
||||||
password: FormControl<string>;
|
username: '',
|
||||||
otp: FormControl<number | null>;
|
password: '',
|
||||||
}>;
|
otp: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.model);
|
||||||
|
|
||||||
hide: boolean;
|
hide: boolean;
|
||||||
showOtp: boolean;
|
showOtp: boolean;
|
||||||
@@ -40,10 +50,8 @@ export class LoginComponent implements OnInit, AfterViewInit {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.hide = true;
|
this.hide = true;
|
||||||
this.showOtp = false;
|
this.showOtp = false;
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
username: new FormControl('', { validators: Validators.required, nonNullable: true }),
|
this.form().focusBoundControl();
|
||||||
password: new FormControl('', { validators: Validators.required, nonNullable: true }),
|
|
||||||
otp: new FormControl(0),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,17 +59,11 @@ export class LoginComponent implements OnInit, AfterViewInit {
|
|||||||
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.nameElement.nativeElement.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
login(): void {
|
login(): void {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
const username = formModel.username ?? '';
|
const username = formModel.username ?? '';
|
||||||
const password = formModel.password ?? '';
|
const password = formModel.password ?? '';
|
||||||
const otp = formModel.otp ?? 0;
|
const otp = Number(formModel.otp) || 0;
|
||||||
this.auth
|
this.auth
|
||||||
.login(username, password, otp)
|
.login(username, password, otp)
|
||||||
// .pipe(first())
|
// .pipe(first())
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
||||||
import { MatSort, Sort } from '@angular/material/sort';
|
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { BalanceSheetItem } from './balance-sheet-item';
|
|
||||||
|
|
||||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
|
||||||
const compare = (a: string | number, b: string | number, isAsc: boolean) => (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
|
||||||
export class BalanceSheetDataSource extends DataSource<BalanceSheetItem> {
|
|
||||||
constructor(
|
|
||||||
public data: BalanceSheetItem[],
|
|
||||||
private paginator?: MatPaginator,
|
|
||||||
private sort?: MatSort,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<BalanceSheetItem[]> {
|
|
||||||
const dataMutations: (EventEmitter<PageEvent> | EventEmitter<Sort>)[] = [];
|
|
||||||
if (this.paginator) {
|
|
||||||
dataMutations.push((this.paginator as MatPaginator).page);
|
|
||||||
}
|
|
||||||
if (this.sort) {
|
|
||||||
dataMutations.push((this.sort as MatSort).sortChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the paginators length
|
|
||||||
if (this.paginator) {
|
|
||||||
this.paginator.length = this.data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return merge(observableOf(this.data), ...dataMutations).pipe(
|
|
||||||
map(() => this.getPagedData(this.getSortedData([...this.data]))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
|
|
||||||
private getPagedData(data: BalanceSheetItem[]) {
|
|
||||||
if (this.paginator === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
|
||||||
return data.splice(startIndex, this.paginator.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSortedData(data: BalanceSheetItem[]) {
|
|
||||||
if (this.sort === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (!this.sort.active || this.sort.direction === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sort = this.sort as MatSort;
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
const isAsc = sort.direction === 'asc';
|
|
||||||
switch (sort.active) {
|
|
||||||
case 'name':
|
|
||||||
if (a.name === undefined || b.name === undefined) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return compare(a.name, b.name, isAsc);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,34 @@
|
|||||||
<h2>Balance Sheet</h2>
|
<h2>Balance Sheet</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Date</mat-label>
|
<mat-label>Date</mat-label>
|
||||||
<input matInput #dateElement [matDatepicker]="dateInput" formControlName="date" autocomplete="off" />
|
<input matInput #dateElement [matDatepicker]="dateInput" [formField]="form.date" autocomplete="off" />
|
||||||
<mat-datepicker-toggle matSuffix [for]="dateInput"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="dateInput"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #dateInput></mat-datepicker>
|
<mat-datepicker #dateInput></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" (click)="show()">Show</button>
|
<button mat-raised-button color="primary" (click)="show()">Show</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
@if (infoResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (infoResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="infoResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
|
||||||
<!-- Group Column -->
|
<!-- Group Column -->
|
||||||
<ng-container matColumnDef="group">
|
<ng-container matColumnDef="group">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Group</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">{{ row.group }}</mat-cell>
|
<mat-cell *matCellDef="let row">{{ row.group }}</mat-cell>
|
||||||
<mat-footer-cell *matFooterCellDef>{{ info.footer?.group }}</mat-footer-cell>
|
<mat-footer-cell *matFooterCellDef>{{ info().footer?.group }}</mat-footer-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Name Column -->
|
<!-- Name Column -->
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">{{ row.name }}</mat-cell>
|
<mat-cell *matCellDef="let row">{{ row.name }}</mat-cell>
|
||||||
<mat-footer-cell *matFooterCellDef>{{ info.footer?.name }}</mat-footer-cell>
|
<mat-footer-cell *matFooterCellDef>{{ info().footer?.name }}</mat-footer-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- SubAmount Column -->
|
<!-- SubAmount Column -->
|
||||||
@@ -31,7 +36,7 @@
|
|||||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Amount</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row" class="right">{{ row.subAmount | currency: 'INR' | clear }}</mat-cell>
|
<mat-cell *matCellDef="let row" class="right">{{ row.subAmount | currency: 'INR' | clear }}</mat-cell>
|
||||||
<mat-footer-cell *matFooterCellDef class="right"
|
<mat-footer-cell *matFooterCellDef class="right"
|
||||||
>{{ info.footer?.subAmount | currency: 'INR' | clear }}
|
>{{ $safeNavigationMigration(info().footer?.subAmount) | currency: 'INR' | clear }}
|
||||||
</mat-footer-cell>
|
</mat-footer-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -40,20 +45,21 @@
|
|||||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Total</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header class="right">Total</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row" class="right">{{ row.amount | currency: 'INR' | clear }}</mat-cell>
|
<mat-cell *matCellDef="let row" class="right">{{ row.amount | currency: 'INR' | clear }}</mat-cell>
|
||||||
<mat-footer-cell *matFooterCellDef class="right"
|
<mat-footer-cell *matFooterCellDef class="right"
|
||||||
>{{ info.footer?.amount | currency: 'INR' | clear }}
|
>{{ $safeNavigationMigration(info().footer?.amount) | currency: 'INR' | clear }}
|
||||||
</mat-footer-cell>
|
</mat-footer-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
<mat-footer-row *matFooterRowDef="displayedColumns"></mat-footer-row>
|
<mat-footer-row *matFooterRowDef="displayedColumns"></mat-footer-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<mat-paginator
|
<mat-paginator
|
||||||
#paginator
|
(page)="handlePageEvent($event)"
|
||||||
[length]="dataSource.data.length"
|
[length]="(this.info().body ?? []).length"
|
||||||
[pageIndex]="0"
|
[pageIndex]="pageIndex()"
|
||||||
[pageSize]="50"
|
[pageSize]="pageSize()"
|
||||||
[pageSizeOptions]="[25, 50, 100, 250, 300, 5000]"
|
[pageSizeOptions]="[25, 50, 100, 250, 300, 5000]"
|
||||||
>
|
>
|
||||||
</mat-paginator>
|
</mat-paginator>
|
||||||
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
import { CurrencyPipe } from '@angular/common';
|
import { CurrencyPipe } from '@angular/common';
|
||||||
import { Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, HostListener, inject, input, computed, linkedSignal, signal, afterNextRender } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
|
export interface BalanceSheetFormData {
|
||||||
|
date: moment.Moment;
|
||||||
|
}
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { ClearPipe } from '../shared/clear.pipe';
|
import { ClearPipe } from '../shared/clear.pipe';
|
||||||
|
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { BalanceSheet } from './balance-sheet';
|
import { BalanceSheet } from './balance-sheet';
|
||||||
import { BalanceSheetDataSource } from './balance-sheet-datasource';
|
import { BalanceSheetService } from './balance-sheet.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-balance-sheet',
|
selector: 'app-balance-sheet',
|
||||||
templateUrl: './balance-sheet.component.html',
|
templateUrl: './balance-sheet.component.html',
|
||||||
styleUrls: ['./balance-sheet.component.css'],
|
styleUrls: ['./balance-sheet.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
FormField,
|
||||||
|
FormRoot,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
@@ -30,20 +36,69 @@ import { BalanceSheetDataSource } from './balance-sheet-datasource';
|
|||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
CurrencyPipe,
|
CurrencyPipe,
|
||||||
ClearPipe,
|
ClearPipe,
|
||||||
|
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BalanceSheetComponent implements OnInit {
|
export class BalanceSheetComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
private ser = inject(BalanceSheetService);
|
||||||
|
pageSize = signal(50);
|
||||||
|
pageIndex = signal(0);
|
||||||
|
date = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
infoResource = this.ser.list(this.date);
|
||||||
|
info = computed(() => this.infoResource.value() ?? new BalanceSheet());
|
||||||
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
sortActive = signal('');
|
||||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
sortDirection = signal('');
|
||||||
@ViewChild('dateElement', { static: true }) dateElement!: ElementRef<HTMLInputElement>;
|
|
||||||
info: BalanceSheet = new BalanceSheet();
|
formModel = linkedSignal<import('./balance-sheet').BalanceSheet, BalanceSheetFormData>({
|
||||||
dataSource: BalanceSheetDataSource = new BalanceSheetDataSource(this.info.body, this.paginator, this.sort);
|
source: this.info,
|
||||||
form: FormGroup<{
|
computation: (info: BalanceSheet): BalanceSheetFormData => ({
|
||||||
date: FormControl<moment.Moment>;
|
date: info.date ? moment(info.date, 'DD-MMM-YYYY') : moment(new Date()),
|
||||||
}>;
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.formModel);
|
||||||
|
sortedList = computed(() => {
|
||||||
|
const data = this.info().body ?? [];
|
||||||
|
const active = this.sortActive();
|
||||||
|
const direction = this.sortDirection();
|
||||||
|
|
||||||
|
if (!active || direction === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const isAsc = direction === 'asc';
|
||||||
|
switch (active) {
|
||||||
|
case 'group':
|
||||||
|
return compare(a.group ?? '', b.group ?? '', isAsc);
|
||||||
|
case 'name':
|
||||||
|
return compare(a.name ?? '', b.name ?? '', isAsc);
|
||||||
|
case 'subAmount':
|
||||||
|
return compare(a.subAmount ?? 0, b.subAmount ?? 0, isAsc);
|
||||||
|
case 'total':
|
||||||
|
return compare(a.amount ?? 0, b.amount ?? 0, isAsc);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource = computed(() => {
|
||||||
|
const data = this.sortedList();
|
||||||
|
const pageIndex = this.pageIndex();
|
||||||
|
const pageSize = this.pageSize();
|
||||||
|
return data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
afterNextRender(() => {
|
||||||
|
this.form().focusBoundControl();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
displayedColumns = ['group', 'name', 'subAmount', 'total'];
|
displayedColumns = ['group', 'name', 'subAmount', 'total'];
|
||||||
@@ -51,26 +106,7 @@ export class BalanceSheetComponent implements OnInit {
|
|||||||
@HostListener('window:keydown.f2', ['$event'])
|
@HostListener('window:keydown.f2', ['$event'])
|
||||||
focusDate(event: Event) {
|
focusDate(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.dateElement.nativeElement.focus();
|
this.form().focusBoundControl();
|
||||||
this.dateElement.nativeElement.select();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.form = new FormGroup({
|
|
||||||
date: new FormControl(moment(new Date()), { nonNullable: true }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as { info: BalanceSheet };
|
|
||||||
|
|
||||||
this.info = data.info;
|
|
||||||
this.form.setValue({
|
|
||||||
date: moment(this.info.date, 'DD-MMM-YYYY'),
|
|
||||||
});
|
|
||||||
this.dataSource = new BalanceSheetDataSource(this.info.body, this.paginator, this.sort);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
@@ -79,7 +115,19 @@ export class BalanceSheetComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDate(): string {
|
getDate(): string {
|
||||||
const formModel = this.form.value;
|
const formModel = this.formModel();
|
||||||
return moment(formModel.date).format('DD-MMM-YYYY');
|
return moment(formModel.date).format('DD-MMM-YYYY');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePageEvent(e: PageEvent) {
|
||||||
|
this.pageSize.set(e.pageSize);
|
||||||
|
this.pageIndex.set(e.pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
this.sortActive.set(sort.active);
|
||||||
|
this.sortDirection.set(sort.direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
||||||
|
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { BalanceSheet } from './balance-sheet';
|
|
||||||
import { balanceSheetResolver } from './balance-sheet.resolver';
|
|
||||||
|
|
||||||
describe('balanceSheetResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<BalanceSheet> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => balanceSheetResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { BalanceSheet } from './balance-sheet';
|
|
||||||
import { BalanceSheetService } from './balance-sheet.service';
|
|
||||||
|
|
||||||
export const balanceSheetResolver: ResolveFn<BalanceSheet> = (route) => {
|
|
||||||
const date = route.paramMap.get('date');
|
|
||||||
return inject(BalanceSheetService).list(date);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { BalanceSheetComponent } from './balance-sheet.component';
|
import { BalanceSheetComponent } from './balance-sheet.component';
|
||||||
import { balanceSheetResolver } from './balance-sheet.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -12,9 +11,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Balance Sheet',
|
permission: 'Balance Sheet',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: balanceSheetResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -24,9 +20,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Balance Sheet',
|
permission: 'Balance Sheet',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: balanceSheetResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,25 +1,17 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
|
||||||
import { BalanceSheet } from './balance-sheet';
|
import { BalanceSheet } from './balance-sheet';
|
||||||
|
|
||||||
const url = '/api/balance-sheet';
|
const url = '/api/balance-sheet';
|
||||||
const serviceName = 'BalanceSheetService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class BalanceSheetService {
|
export class BalanceSheetService {
|
||||||
private http = inject(HttpClient);
|
list(date: Signal<string | null>) {
|
||||||
private log = inject(ErrorLoggerService);
|
return httpResource<BalanceSheet>(() => {
|
||||||
|
const listUrl = date() === null ? url : `${url}/${date()}`;
|
||||||
list(date: string | null): Observable<BalanceSheet> {
|
return listUrl;
|
||||||
const listUrl = date === null ? url : `${url}/${date}`;
|
});
|
||||||
return this.http
|
|
||||||
.get<BalanceSheet>(listUrl)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<BalanceSheet>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
import { BatchIntegrity } from './batch-integrity';
|
|
||||||
|
|
||||||
export class BatchIntegrityReportDatasource extends DataSource<BatchIntegrity> {
|
|
||||||
constructor(public data: BatchIntegrity[]) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<BatchIntegrity[]> {
|
|
||||||
return observableOf(this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
<h2>Batch Integrity</h2>
|
<h2>Batch Integrity</h2>
|
||||||
|
|
||||||
<mat-table [dataSource]="dataSource" aria-label="Elements">
|
@if (infoResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (infoResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="infoResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table [dataSource]="info()">
|
||||||
<!-- Batch Column -->
|
<!-- Batch Column -->
|
||||||
<ng-container matColumnDef="batch">
|
<ng-container matColumnDef="batch">
|
||||||
<mat-header-cell *matHeaderCellDef>Batch</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef>Batch</mat-header-cell>
|
||||||
@@ -27,4 +32,5 @@
|
|||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
}
|
||||||
@@ -1,32 +1,27 @@
|
|||||||
import { CurrencyPipe } from '@angular/common';
|
import { CurrencyPipe } from '@angular/common';
|
||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, computed } from '@angular/core';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { BatchIntegrity } from './batch-integrity';
|
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||||
import { BatchIntegrityReportDatasource } from './batch-integrity-report-datasource';
|
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||||
|
import { BatchIntegrityReportService } from './batch-integrity-report.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-batch-integrity',
|
selector: 'app-batch-integrity',
|
||||||
templateUrl: './batch-integrity-report.component.html',
|
templateUrl: './batch-integrity-report.component.html',
|
||||||
styleUrls: ['./batch-integrity-report.component.css'],
|
styleUrls: ['./batch-integrity-report.component.css'],
|
||||||
imports: [MatTableModule, RouterModule, CurrencyPipe],
|
imports: [MatTableModule, RouterModule, CurrencyPipe, SkeletonLoaderComponent, ErrorStateComponent],
|
||||||
})
|
})
|
||||||
export class BatchIntegrityReportComponent implements OnInit {
|
export class BatchIntegrityReportComponent {
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
private ser = inject(BatchIntegrityReportService);
|
||||||
|
|
||||||
info: BatchIntegrity[] = [];
|
|
||||||
dataSource: BatchIntegrityReportDatasource = new BatchIntegrityReportDatasource(this.info);
|
|
||||||
displayedColumns = ['batch', 'details'];
|
displayedColumns = ['batch', 'details'];
|
||||||
|
|
||||||
ngOnInit() {
|
infoResource = this.ser.batchIntegrity();
|
||||||
this.route.data.subscribe((value) => {
|
info = computed(() => this.infoResource.value() ?? []);
|
||||||
const data = value as { info: BatchIntegrity[] };
|
|
||||||
this.info = data.info;
|
|
||||||
this.dataSource = new BatchIntegrityReportDatasource(this.info);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.router.navigate([]);
|
this.router.navigate([]);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { BatchIntegrityReportComponent } from './batch-integrity-report.component';
|
import { BatchIntegrityReportComponent } from './batch-integrity-report.component';
|
||||||
import { batchIntegrityResolver } from './batch-integrity.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -12,9 +11,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Product Ledger',
|
permission: 'Product Ledger',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: batchIntegrityResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
|
||||||
import { BatchIntegrity } from './batch-integrity';
|
import { BatchIntegrity } from './batch-integrity';
|
||||||
|
|
||||||
const serviceName = 'BatchIntegrityReportService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class BatchIntegrityReportService {
|
export class BatchIntegrityReportService {
|
||||||
private http = inject(HttpClient);
|
batchIntegrity() {
|
||||||
private log = inject(ErrorLoggerService);
|
|
||||||
|
|
||||||
batchIntegrity(): Observable<BatchIntegrity[]> {
|
|
||||||
const url = '/api/batch-integrity';
|
const url = '/api/batch-integrity';
|
||||||
return this.http
|
return httpResource<BatchIntegrity[]>(() => ({
|
||||||
.post<BatchIntegrity[]>(url, {})
|
url,
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'batchIntegrity'))) as Observable<BatchIntegrity[]>;
|
method: 'POST',
|
||||||
|
body: {},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { BatchIntegrity } from './batch-integrity';
|
|
||||||
import { batchIntegrityResolver } from './batch-integrity.resolver';
|
|
||||||
|
|
||||||
describe('batchIntegrityResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<BatchIntegrity[]> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => batchIntegrityResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { BatchIntegrity } from './batch-integrity';
|
|
||||||
import { BatchIntegrityReportService } from './batch-integrity-report.service';
|
|
||||||
|
|
||||||
export const batchIntegrityResolver: ResolveFn<BatchIntegrity[]> = () => {
|
|
||||||
return inject(BatchIntegrityReportService).batchIntegrity();
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
import { CashFlowItem } from './cash-flow-item';
|
|
||||||
|
|
||||||
export class CashFlowDataSource extends DataSource<CashFlowItem> {
|
|
||||||
constructor(private data: CashFlowItem[]) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<CashFlowItem[]> {
|
|
||||||
return observableOf(this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,33 @@
|
|||||||
<h2>Cash Flow</h2>
|
<h2>Cash Flow</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto basis-2-5">
|
<mat-form-field class="flex-auto basis-2-5">
|
||||||
<mat-label>Start Date</mat-label>
|
<mat-label>Start Date</mat-label>
|
||||||
<input matInput #startDateElement [matDatepicker]="startDate" formControlName="startDate" autocomplete="off" />
|
<input matInput [matDatepicker]="startDatePicker" [formField]="form.startDate" autocomplete="off" />
|
||||||
<mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #startDate></mat-datepicker>
|
<mat-datepicker #startDatePicker></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto basis-2-5">
|
<mat-form-field class="flex-auto basis-2-5">
|
||||||
<mat-label>Finish Date</mat-label>
|
<mat-label>Finish Date</mat-label>
|
||||||
<input matInput [matDatepicker]="finishDate" formControlName="finishDate" autocomplete="off" />
|
<input matInput [matDatepicker]="finishDate" [formField]="form.finishDate" autocomplete="off" />
|
||||||
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="finishDate"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #finishDate></mat-datepicker>
|
<mat-datepicker #finishDate></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
@if (infoResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (infoResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load cash flow.'" (retryAction)="infoResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
|
||||||
<!-- Name Column -->
|
<!-- Name Column -->
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row"
|
<mat-cell *matCellDef="let row"
|
||||||
><a [routerLink]="row.url" [queryParams]="{ startDate: info.startDate, finishDate: info.finishDate }"
|
><a [routerLink]="row.url" [queryParams]="{ startDate: info().startDate, finishDate: info().finishDate }"
|
||||||
>{{ row.name }}
|
>{{ row.name }}
|
||||||
</a></mat-cell
|
</a></mat-cell
|
||||||
>
|
>
|
||||||
@@ -36,4 +41,5 @@
|
|||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
}
|
||||||
@@ -1,25 +1,32 @@
|
|||||||
import { CurrencyPipe } from '@angular/common';
|
import { CurrencyPipe } from '@angular/common';
|
||||||
import { Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, HostListener, inject, input, computed, linkedSignal, afterNextRender, signal } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
|
export interface CashFlowFormData {
|
||||||
|
startDate: moment.Moment;
|
||||||
|
finishDate: moment.Moment;
|
||||||
|
}
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { Router, RouterModule } from '@angular/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { ClearPipe } from '../shared/clear.pipe';
|
import { ClearPipe } from '../shared/clear.pipe';
|
||||||
|
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { CashFlow } from './cash-flow';
|
import { CashFlow } from './cash-flow';
|
||||||
import { CashFlowDataSource } from './cash-flow-datasource';
|
import { CashFlowService } from './cash-flow.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-cash-flow',
|
selector: 'app-cash-flow',
|
||||||
templateUrl: './cash-flow.component.html',
|
templateUrl: './cash-flow.component.html',
|
||||||
styleUrls: ['./cash-flow.component.css'],
|
styleUrls: ['./cash-flow.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
FormField,
|
||||||
|
FormRoot,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
@@ -29,19 +36,54 @@ import { CashFlowDataSource } from './cash-flow-datasource';
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
CurrencyPipe,
|
CurrencyPipe,
|
||||||
ClearPipe,
|
ClearPipe,
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CashFlowComponent implements OnInit {
|
export class CashFlowComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
private ser = inject(CashFlowService);
|
||||||
|
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
startDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
finishDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
sortActive = signal('');
|
||||||
|
sortDirection = signal('');
|
||||||
|
|
||||||
@ViewChild('startDateElement', { static: true }) startDate!: ElementRef<HTMLInputElement>;
|
infoResource = this.ser.list(this.id, this.startDate, this.finishDate);
|
||||||
info: CashFlow = new CashFlow();
|
info = computed(() => this.infoResource.value() ?? new CashFlow());
|
||||||
dataSource: CashFlowDataSource = new CashFlowDataSource(CashFlow.Data(this.info));
|
sortedList = computed(() => {
|
||||||
form: FormGroup<{
|
const data = CashFlow.Data(this.info());
|
||||||
startDate: FormControl<moment.Moment>;
|
const active = this.sortActive();
|
||||||
finishDate: FormControl<moment.Moment>;
|
const direction = this.sortDirection();
|
||||||
}>;
|
|
||||||
|
if (!active || direction === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const isAsc = direction === 'asc';
|
||||||
|
switch (active) {
|
||||||
|
case 'name':
|
||||||
|
return compare(a.name ?? '', b.name ?? '', isAsc);
|
||||||
|
case 'amount':
|
||||||
|
return compare(a.amount ?? 0, b.amount ?? 0, isAsc);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource = computed(() => this.sortedList());
|
||||||
|
|
||||||
|
model = linkedSignal({
|
||||||
|
source: this.info,
|
||||||
|
computation: (info: CashFlow): CashFlowFormData => ({
|
||||||
|
startDate: info.startDate ? moment(info.startDate, 'DD-MMM-YYYY') : moment(new Date()),
|
||||||
|
finishDate: info.finishDate ? moment(info.finishDate, 'DD-MMM-YYYY') : moment(new Date()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.model);
|
||||||
|
|
||||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
displayedColumns = ['name', 'amount'];
|
displayedColumns = ['name', 'amount'];
|
||||||
@@ -49,27 +91,13 @@ export class CashFlowComponent implements OnInit {
|
|||||||
@HostListener('window:keydown.f2', ['$event'])
|
@HostListener('window:keydown.f2', ['$event'])
|
||||||
focusDate(event: Event) {
|
focusDate(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.startDate.nativeElement.focus();
|
// TODO: also select the contents
|
||||||
this.startDate.nativeElement.select();
|
this.form.startDate().focusBoundControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
startDate: new FormControl(moment(new Date()), { nonNullable: true }),
|
this.form.startDate().focusBoundControl();
|
||||||
finishDate: new FormControl(moment(new Date()), { nonNullable: true }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as { info: CashFlow };
|
|
||||||
|
|
||||||
this.info = data.info;
|
|
||||||
this.form.setValue({
|
|
||||||
startDate: moment(this.info.startDate, 'DD-MMM-YYYY'),
|
|
||||||
finishDate: moment(this.info.finishDate, 'DD-MMM-YYYY'),
|
|
||||||
});
|
|
||||||
this.dataSource = new CashFlowDataSource(CashFlow.Data(this.info));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,11 +112,18 @@ export class CashFlowComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getInfo(): CashFlow {
|
getInfo(): CashFlow {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
|
|
||||||
return new CashFlow({
|
return new CashFlow({
|
||||||
startDate: moment(formModel.startDate).format('DD-MMM-YYYY'),
|
startDate: moment(formModel.startDate).format('DD-MMM-YYYY'),
|
||||||
finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'),
|
finishDate: moment(formModel.finishDate).format('DD-MMM-YYYY'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
this.sortActive.set(sort.active);
|
||||||
|
this.sortDirection.set(sort.direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
||||||
|
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { CashFlow } from './cash-flow';
|
|
||||||
import { cashFlowResolver } from './cash-flow.resolver';
|
|
||||||
|
|
||||||
describe('cashFlowResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<CashFlow> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => cashFlowResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { CashFlow } from './cash-flow';
|
|
||||||
import { CashFlowService } from './cash-flow.service';
|
|
||||||
|
|
||||||
export const cashFlowResolver: ResolveFn<CashFlow> = (route) => {
|
|
||||||
const id = route.paramMap.get('id');
|
|
||||||
const startDate = route.queryParamMap.get('startDate') || null;
|
|
||||||
const finishDate = route.queryParamMap.get('finishDate') || null;
|
|
||||||
return inject(CashFlowService).list(id, startDate, finishDate);
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,6 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { CashFlowComponent } from './cash-flow.component';
|
import { CashFlowComponent } from './cash-flow.component';
|
||||||
import { cashFlowResolver } from './cash-flow.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -12,9 +11,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Cash Flow',
|
permission: 'Cash Flow',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: cashFlowResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -24,9 +20,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Cash Flow',
|
permission: 'Cash Flow',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: cashFlowResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,39 +1,29 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
|
||||||
import { CashFlow } from './cash-flow';
|
import { CashFlow } from './cash-flow';
|
||||||
|
|
||||||
const url = '/api/cash-flow';
|
const url = '/api/cash-flow';
|
||||||
const serviceName = 'CashFlowService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class CashFlowService {
|
export class CashFlowService {
|
||||||
private http = inject(HttpClient);
|
list(id: Signal<string | null>, startDate: Signal<string | null>, finishDate: Signal<string | null>) {
|
||||||
private log = inject(ErrorLoggerService);
|
return httpResource<CashFlow>(() => {
|
||||||
|
const params: Record<string, string> = {};
|
||||||
list(id: string | null, startDate: string | null, finishDate: string | null): Observable<CashFlow> {
|
const startDate_val = startDate();
|
||||||
const options = { params: new HttpParams() };
|
if (startDate_val !== null) params['s'] = startDate_val;
|
||||||
if (startDate !== null) {
|
const finishDate_val = finishDate();
|
||||||
options.params = options.params.set('s', startDate);
|
if (finishDate_val !== null) params['f'] = finishDate_val;
|
||||||
}
|
let resultUrl: string;
|
||||||
if (finishDate !== null) {
|
if (id() !== null) {
|
||||||
options.params = options.params.set('f', finishDate);
|
resultUrl = `${url}/${id()}`;
|
||||||
}
|
} else if (startDate() || finishDate()) {
|
||||||
let listUrl = '';
|
resultUrl = `${url}/data`;
|
||||||
if (id !== null) {
|
|
||||||
listUrl = `${url}/${id}`;
|
|
||||||
} else if (startDate || finishDate) {
|
|
||||||
listUrl = `${url}/data`;
|
|
||||||
} else {
|
} else {
|
||||||
listUrl = url;
|
resultUrl = url;
|
||||||
}
|
}
|
||||||
return this.http
|
return { url: resultUrl, params };
|
||||||
.get<CashFlow>(listUrl, options)
|
});
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<CashFlow>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,37 @@
|
|||||||
<h2>Client</h2>
|
<h2>Client</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col wrapped">
|
@if (itemResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="card" [count]="1" />
|
||||||
|
<app-skeleton-loader type="line" [count]="10" />
|
||||||
|
} @else if (itemResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="itemResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<form class="flex-col wrapped" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Code</mat-label>
|
<mat-label>Code</mat-label>
|
||||||
<input matInput formControlName="code" />
|
<input matInput [formField]="form.code" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input matInput #nameElement formControlName="name" />
|
<input matInput [formField]="form.name" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-checkbox formControlName="enabled" class="flex-auto">Is Enabled?</mat-checkbox>
|
<mat-checkbox [formField]="form.enabled" class="flex-auto">Is Enabled?</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>OTP</mat-label>
|
<mat-label>OTP</mat-label>
|
||||||
<input matInput formControlName="otp" />
|
<input matInput [formField]="form.otp" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||||
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
<button mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
@@ -1,73 +1,71 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, inject, afterNextRender, linkedSignal, input, computed } from '@angular/core';
|
||||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../../shared/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { Client } from '../client';
|
import { Client } from '../client';
|
||||||
import { ClientService } from '../client.service';
|
import { ClientService } from '../client.service';
|
||||||
|
|
||||||
|
export interface ClientFormData {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
otp: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-client-detail',
|
selector: 'app-client-detail',
|
||||||
templateUrl: './client-detail.component.html',
|
templateUrl: './client-detail.component.html',
|
||||||
styleUrls: ['./client-detail.component.css'],
|
styleUrls: ['./client-detail.component.css'],
|
||||||
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, MatButtonModule],
|
imports: [
|
||||||
|
FormField,
|
||||||
|
FormRoot,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
MatButtonModule,
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ClientDetailComponent implements OnInit, AfterViewInit {
|
export class ClientDetailComponent {
|
||||||
private route = inject(ActivatedRoute);
|
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
private ser = inject(ClientService);
|
private ser = inject(ClientService);
|
||||||
|
|
||||||
@ViewChild('nameElement', { static: true }) nameElement!: ElementRef<HTMLInputElement>;
|
itemResource = this.ser.get(this.id);
|
||||||
form: FormGroup<{
|
|
||||||
code: FormControl<number | string>;
|
|
||||||
name: FormControl<string | null>;
|
|
||||||
enabled: FormControl<boolean>;
|
|
||||||
otp: FormControl<number | null>;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
item: Client = new Client();
|
item = computed(() => this.itemResource.value() ?? new Client());
|
||||||
|
|
||||||
|
model = linkedSignal({
|
||||||
|
source: this.item,
|
||||||
|
computation: (item): ClientFormData => ({
|
||||||
|
code: item.code ? String(item.code) : '(Auto)',
|
||||||
|
name: item.name ?? '',
|
||||||
|
enabled: item.enabled ?? false,
|
||||||
|
otp: item.otp ? String(item.otp) : '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.model);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.form = new FormGroup({
|
afterNextRender(() => {
|
||||||
code: new FormControl<number | string>({ value: '(Auto)', disabled: true }, { nonNullable: true }),
|
this.form.name().focusBoundControl();
|
||||||
name: new FormControl<string | null>(null),
|
|
||||||
enabled: new FormControl<boolean>(false, { nonNullable: true }),
|
|
||||||
otp: new FormControl<number | null>(null),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as { item: Client };
|
|
||||||
|
|
||||||
this.showItem(data.item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showItem(item: Client) {
|
|
||||||
this.item = item;
|
|
||||||
this.form.setValue({
|
|
||||||
code: this.item.code || '(Auto)',
|
|
||||||
name: this.item.name,
|
|
||||||
enabled: this.item.enabled,
|
|
||||||
otp: this.item.otp || null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit() {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.nameElement.nativeElement.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
this.ser.update(this.getItem()).subscribe({
|
this.ser.update(this.getItem()).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
@@ -75,19 +73,19 @@ export class ClientDetailComponent implements OnInit, AfterViewInit {
|
|||||||
this.router.navigateByUrl('/clients');
|
this.router.navigateByUrl('/clients');
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.snackBar.open(error, 'Danger');
|
this.snackBar.open(error as string, 'Danger');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
this.ser.delete(this.item.id).subscribe({
|
this.ser.delete(this.item().id).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('', 'Success');
|
this.snackBar.open('', 'Success');
|
||||||
this.router.navigateByUrl('/clients');
|
this.router.navigateByUrl('/clients');
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
this.snackBar.open(error, 'Danger');
|
this.snackBar.open(error as string, 'Danger');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,10 +104,11 @@ export class ClientDetailComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getItem(): Client {
|
getItem(): Client {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
this.item.name = formModel.name ?? '';
|
const item = this.item();
|
||||||
this.item.enabled = formModel.enabled ?? false;
|
item.name = formModel.name ?? '';
|
||||||
this.item.otp = formModel.otp || undefined;
|
item.enabled = formModel.enabled ?? false;
|
||||||
return this.item;
|
item.otp = formModel.otp ? Number(formModel.otp) : undefined;
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Client } from './client';
|
|
||||||
import { clientListResolver } from './client-list.resolver';
|
|
||||||
|
|
||||||
describe('clientListResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<Client[]> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => clientListResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Client } from './client';
|
|
||||||
import { ClientService } from './client.service';
|
|
||||||
|
|
||||||
export const clientListResolver: ResolveFn<Client[]> = () => {
|
|
||||||
return inject(ClientService).list();
|
|
||||||
};
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
||||||
import { MatSort, Sort } from '@angular/material/sort';
|
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Client } from '../client';
|
|
||||||
|
|
||||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
|
||||||
const compare = (a: string | number, b: string | number, isAsc: boolean) => (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
|
||||||
export class ClientListDataSource extends DataSource<Client> {
|
|
||||||
constructor(
|
|
||||||
public data: Client[],
|
|
||||||
private paginator?: MatPaginator,
|
|
||||||
private sort?: MatSort,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<Client[]> {
|
|
||||||
const dataMutations: (EventEmitter<PageEvent> | EventEmitter<Sort>)[] = [];
|
|
||||||
if (this.paginator) {
|
|
||||||
dataMutations.push((this.paginator as MatPaginator).page);
|
|
||||||
}
|
|
||||||
if (this.sort) {
|
|
||||||
dataMutations.push((this.sort as MatSort).sortChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the paginators length
|
|
||||||
if (this.paginator) {
|
|
||||||
this.paginator.length = this.data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return merge(observableOf(this.data), ...dataMutations).pipe(
|
|
||||||
map(() => this.getPagedData(this.getSortedData([...this.data]))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
|
|
||||||
private getPagedData(data: Client[]) {
|
|
||||||
if (this.paginator === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
|
||||||
return data.splice(startIndex, this.paginator.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSortedData(data: Client[]) {
|
|
||||||
if (this.sort === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (!this.sort.active || this.sort.direction === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sort = this.sort as MatSort;
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
const isAsc = sort.direction === 'asc';
|
|
||||||
switch (sort.active) {
|
|
||||||
case 'name':
|
|
||||||
return compare(a.name, b.name, isAsc);
|
|
||||||
case 'id':
|
|
||||||
return compare(+a.id, +b.id, isAsc);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
<h2>Clients</h2>
|
<h2>Clients</h2>
|
||||||
|
|
||||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements">
|
@if (listResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (listResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="listResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
|
||||||
<!-- Code Column -->
|
<!-- Code Column -->
|
||||||
<ng-container matColumnDef="code">
|
<ng-container matColumnDef="code">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Code</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header>Code</mat-header-cell>
|
||||||
@@ -43,13 +48,14 @@
|
|||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<mat-paginator
|
<mat-paginator
|
||||||
#paginator
|
(page)="handlePageEvent($event)"
|
||||||
[length]="dataSource.data.length"
|
[length]="list().length"
|
||||||
[pageIndex]="0"
|
[pageIndex]="pageIndex()"
|
||||||
[pageSize]="50"
|
[pageSize]="pageSize()"
|
||||||
[pageSizeOptions]="[25, 50, 100, 250]"
|
[pageSizeOptions]="[25, 50, 100, 250]"
|
||||||
>
|
>
|
||||||
</mat-paginator>
|
</mat-paginator>
|
||||||
|
}
|
||||||
@@ -1,35 +1,81 @@
|
|||||||
import { Component, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { ErrorStateComponent } from '../../shared/error-state/error-state.component';
|
||||||
import { LocalTimePipe } from '../../shared/local-time.pipe';
|
import { LocalTimePipe } from '../../shared/local-time.pipe';
|
||||||
import { Client } from '../client';
|
import { SkeletonLoaderComponent } from '../../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { ClientListDataSource } from './client-list-datasource';
|
import { ClientService } from '../client.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-client-list',
|
selector: 'app-client-list',
|
||||||
templateUrl: './client-list.component.html',
|
templateUrl: './client-list.component.html',
|
||||||
styleUrls: ['./client-list.component.css'],
|
styleUrls: ['./client-list.component.css'],
|
||||||
imports: [MatTableModule, MatSortModule, RouterModule, MatPaginatorModule, LocalTimePipe],
|
imports: [
|
||||||
|
MatTableModule,
|
||||||
|
MatSortModule,
|
||||||
|
RouterModule,
|
||||||
|
MatPaginatorModule,
|
||||||
|
LocalTimePipe,
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ClientListComponent implements OnInit {
|
export class ClientListComponent {
|
||||||
private route = inject(ActivatedRoute);
|
private ser = inject(ClientService);
|
||||||
|
|
||||||
|
pageSize = signal(50);
|
||||||
|
pageIndex = signal(0);
|
||||||
|
sortActive = signal('');
|
||||||
|
sortDirection = signal('');
|
||||||
|
listResource = this.ser.list();
|
||||||
|
list = computed(() => this.listResource.value() ?? []);
|
||||||
|
sortedList = computed(() => {
|
||||||
|
const data = this.list();
|
||||||
|
const active = this.sortActive();
|
||||||
|
const direction = this.sortDirection();
|
||||||
|
|
||||||
|
if (!active || direction === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const isAsc = direction === 'asc';
|
||||||
|
switch (active) {
|
||||||
|
case 'name':
|
||||||
|
return compare(a.name, b.name, isAsc);
|
||||||
|
case 'enabled':
|
||||||
|
return compare(a.enabled, b.enabled, isAsc);
|
||||||
|
case 'creationDate':
|
||||||
|
return compare(a.creationDate, b.creationDate, isAsc);
|
||||||
|
case 'lastDate':
|
||||||
|
return compare(a.lastDate ?? 0, b.lastDate ?? 0, isAsc);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource = computed(() => {
|
||||||
|
const data = this.sortedList();
|
||||||
|
const pageIndex = this.pageIndex();
|
||||||
|
const pageSize = this.pageSize();
|
||||||
|
return data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
|
||||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
|
||||||
list: Client[] = [];
|
|
||||||
dataSource: ClientListDataSource = new ClientListDataSource(this.list, this.paginator, this.sort);
|
|
||||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'last'];
|
displayedColumns = ['code', 'name', 'enabled', 'otp', 'created', 'last'];
|
||||||
|
handlePageEvent(e: PageEvent) {
|
||||||
|
this.pageSize.set(e.pageSize);
|
||||||
|
this.pageIndex.set(e.pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
sortData(sort: Sort) {
|
||||||
this.route.data.subscribe((value) => {
|
this.sortActive.set(sort.active);
|
||||||
const data = value as { list: Client[] };
|
this.sortDirection.set(sort.direction);
|
||||||
|
|
||||||
this.list = data.list;
|
|
||||||
});
|
|
||||||
this.dataSource = new ClientListDataSource(this.list, this.paginator, this.sort);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
||||||
|
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Client } from './client';
|
|
||||||
import { clientResolver } from './client.resolver';
|
|
||||||
|
|
||||||
describe('clientResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<Client> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => clientResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { Client } from './client';
|
|
||||||
import { ClientService } from './client.service';
|
|
||||||
|
|
||||||
export const clientResolver: ResolveFn<Client> = (route) => {
|
|
||||||
const id = route.paramMap.get('id');
|
|
||||||
return inject(ClientService).get(id);
|
|
||||||
};
|
|
||||||
@@ -2,9 +2,7 @@ import { Routes } from '@angular/router';
|
|||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { ClientDetailComponent } from './client-detail/client-detail.component';
|
import { ClientDetailComponent } from './client-detail/client-detail.component';
|
||||||
import { clientListResolver } from './client-list.resolver';
|
|
||||||
import { ClientListComponent } from './client-list/client-list.component';
|
import { ClientListComponent } from './client-list/client-list.component';
|
||||||
import { clientResolver } from './client.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -14,9 +12,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Cost Centres',
|
permission: 'Cost Centres',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
list: clientListResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
@@ -25,8 +20,5 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Cost Centres',
|
permission: 'Cost Centres',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
item: clientResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -7,8 +7,6 @@ import { ErrorLoggerService } from '../core/error-logger.service';
|
|||||||
import { Client } from './client';
|
import { Client } from './client';
|
||||||
|
|
||||||
const url = '/api/clients';
|
const url = '/api/clients';
|
||||||
const serviceName = 'ClientService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -16,28 +14,26 @@ export class ClientService {
|
|||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
get(id: string | null): Observable<Client> {
|
get(id: Signal<string | null>) {
|
||||||
const getUrl: string = id === null ? url : `${url}/${id}`;
|
return httpResource<Client>(() => {
|
||||||
return this.http
|
const getUrl: string = id() === null ? url : `${url}/${id()}`;
|
||||||
.get<Client>(getUrl)
|
return getUrl;
|
||||||
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Client>;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
list(): Observable<Client[]> {
|
list() {
|
||||||
return this.http
|
return httpResource<Client[]>(() => `${url}/list`);
|
||||||
.get<Client[]>(`${url}/list`)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Client[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(client: Client): Observable<Client> {
|
update(client: Client): Observable<Client> {
|
||||||
return this.http
|
return this.http
|
||||||
.put<Client>(`${url}/${client.id}`, client)
|
.put<Client>(`${url}/${client.id}`, client)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<Client>;
|
.pipe(catchError(this.log.handleError('ClientService', 'update'))) as Observable<Client>;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(id: string): Observable<Client> {
|
delete(id: string): Observable<Client> {
|
||||||
return this.http
|
return this.http
|
||||||
.delete<Client>(`${url}/${id}`)
|
.delete<Client>(`${url}/${id}`)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<Client>;
|
.pipe(catchError(this.log.handleError('ClientService', 'delete'))) as Observable<Client>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import { DataSource } from '@angular/cdk/collections';
|
|
||||||
import { EventEmitter } from '@angular/core';
|
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
||||||
import { MatSort, Sort } from '@angular/material/sort';
|
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ClosingStockItem } from './closing-stock-item';
|
|
||||||
|
|
||||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
|
||||||
const compare = (a: string | number, b: string | number, isAsc: boolean) => (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
|
||||||
export class ClosingStockDataSource extends DataSource<ClosingStockItem> {
|
|
||||||
constructor(
|
|
||||||
public data: ClosingStockItem[],
|
|
||||||
private paginator?: MatPaginator,
|
|
||||||
private sort?: MatSort,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Observable<ClosingStockItem[]> {
|
|
||||||
const dataMutations: (EventEmitter<PageEvent> | EventEmitter<Sort>)[] = [];
|
|
||||||
if (this.paginator) {
|
|
||||||
dataMutations.push((this.paginator as MatPaginator).page);
|
|
||||||
}
|
|
||||||
if (this.sort) {
|
|
||||||
dataMutations.push((this.sort as MatSort).sortChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the paginators length
|
|
||||||
if (this.paginator) {
|
|
||||||
this.paginator.length = this.data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return merge(observableOf(this.data), ...dataMutations).pipe(
|
|
||||||
map(() => this.getPagedData(this.getSortedData([...this.data]))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {}
|
|
||||||
|
|
||||||
private getPagedData(data: ClosingStockItem[]) {
|
|
||||||
if (this.paginator === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
|
||||||
return data.splice(startIndex, this.paginator.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSortedData(data: ClosingStockItem[]) {
|
|
||||||
if (this.sort === undefined) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
if (!this.sort.active || this.sort.direction === '') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sort = this.sort as MatSort;
|
|
||||||
return data.sort((a, b) => {
|
|
||||||
const isAsc = sort.direction === 'asc';
|
|
||||||
switch (sort.active) {
|
|
||||||
case 'product':
|
|
||||||
return compare(`${a.group} - ${a.product}`, `${b.group} - ${b.product}`, isAsc);
|
|
||||||
case 'quantity':
|
|
||||||
return compare(+a.quantity, +b.quantity, isAsc);
|
|
||||||
case 'amount':
|
|
||||||
return compare(+a.amount, +b.amount, isAsc);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<h2 class="row-container space-between">
|
<h2 class="row-container space-between">
|
||||||
<span>Closing Stock</span>
|
<span>Closing Stock</span>
|
||||||
@if (dataSource.data.length) {
|
@if ((this.info().items ?? []).length) {
|
||||||
<button mat-icon-button (click)="exportCsv()">
|
<button mat-icon-button (click)="exportCsv()">
|
||||||
<mat-icon>save_alt</mat-icon>
|
<mat-icon>save_alt</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col">
|
<form class="flex-col" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto basis-2-5">
|
<mat-form-field class="flex-auto basis-2-5">
|
||||||
<mat-label>Department</mat-label>
|
<mat-label>Department</mat-label>
|
||||||
<mat-select formControlName="costCentre" name="costCentre">
|
<mat-select [formField]="form.costCentre">
|
||||||
@for (at of costCentres; track at) {
|
@for (at of costCentres(); track at) {
|
||||||
<mat-option [value]="at.id">
|
<mat-option [value]="at.id">
|
||||||
{{ at.name }}
|
{{ at.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@@ -21,13 +21,18 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto basis-2-5">
|
<mat-form-field class="flex-auto basis-2-5">
|
||||||
<mat-label>Date</mat-label>
|
<mat-label>Date</mat-label>
|
||||||
<input matInput #dateElement [matDatepicker]="dateInput" formControlName="date" autocomplete="off" />
|
<input matInput #dateElement [matDatepicker]="dateInput" [formField]="form.date" autocomplete="off" />
|
||||||
<mat-datepicker-toggle matSuffix [for]="dateInput"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="dateInput"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #dateInput></mat-datepicker>
|
<mat-datepicker #dateInput></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
<button mat-raised-button color="primary" (click)="show()" class="flex-auto basis-1-5">Show</button>
|
||||||
</div>
|
</div>
|
||||||
<mat-table #table [dataSource]="dataSource" matSort aria-label="Elements" formArrayName="stocks">
|
@if (infoResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="table" [count]="1" />
|
||||||
|
} @else if (infoResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="infoResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<mat-table #table [dataSource]="dataSource()" matSort (matSortChange)="sortData($event)">
|
||||||
<!-- Product Column -->
|
<!-- Product Column -->
|
||||||
<ng-container matColumnDef="product" class="first">
|
<ng-container matColumnDef="product" class="first">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header class="first">Product</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header class="first">Product</mat-header-cell>
|
||||||
@@ -49,7 +54,7 @@
|
|||||||
<!-- Physical Column -->
|
<!-- Physical Column -->
|
||||||
<ng-container matColumnDef="physical">
|
<ng-container matColumnDef="physical">
|
||||||
<mat-header-cell *matHeaderCellDef class="middle">Physical Stock</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef class="middle">Physical Stock</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
<mat-cell *matCellDef="let row; let i = index" class="middle">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Physical</mat-label>
|
<mat-label>Physical</mat-label>
|
||||||
<input matInput type="number" formControlName="physical" (change)="updatePhysical($event, row)" />
|
<input matInput type="number" formControlName="physical" (change)="updatePhysical($event, row)" />
|
||||||
@@ -68,15 +73,11 @@
|
|||||||
<!-- Department Column -->
|
<!-- Department Column -->
|
||||||
<ng-container matColumnDef="department">
|
<ng-container matColumnDef="department">
|
||||||
<mat-header-cell *matHeaderCellDef class="middle">Department</mat-header-cell>
|
<mat-header-cell *matHeaderCellDef class="middle">Department</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row; let i = index" [formGroupName]="i" class="middle">
|
<mat-cell *matCellDef="let row; let i = index" class="middle">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Department</mat-label>
|
<mat-label>Department</mat-label>
|
||||||
<mat-select
|
<mat-select [formField]="form.costCentre" (selectionChange)="updateDepartment($event.value, row)">
|
||||||
formControlName="costCentre"
|
@for (at of costCentres(); track at) {
|
||||||
name="costCentre"
|
|
||||||
(selectionChange)="updateDepartment($event.value, row)"
|
|
||||||
>
|
|
||||||
@for (at of costCentres; track at) {
|
|
||||||
<mat-option [value]="at.id">
|
<mat-option [value]="at.id">
|
||||||
{{ at.name }}
|
{{ at.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
@@ -97,20 +98,21 @@
|
|||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<mat-paginator
|
<mat-paginator
|
||||||
#paginator
|
(page)="handlePageEvent($event)"
|
||||||
[length]="dataSource.data.length"
|
[length]="(this.info().items ?? []).length"
|
||||||
[pageIndex]="0"
|
[pageIndex]="pageIndex()"
|
||||||
[pageSize]="50"
|
[pageSize]="pageSize()"
|
||||||
[pageSizeOptions]="[25, 50, 100, 250, 300, 5000]"
|
[pageSizeOptions]="[25, 50, 100, 250, 300, 5000]"
|
||||||
>
|
>
|
||||||
</mat-paginator>
|
</mat-paginator>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<button mat-raised-button color="primary" (click)="save()" [disabled]="form.pristine">Save</button>
|
<button mat-raised-button color="primary" (click)="save()" [disabled]="!$any(form).dirty">Save</button>
|
||||||
@if (canDelete()) {
|
@if (canDelete()) {
|
||||||
<button mat-raised-button (click)="post()" [disabled]="info.posted || !auth.allowed('post-vouchers')">
|
<button mat-raised-button (click)="post()" [disabled]="info().posted || !auth.allowed('post-vouchers')">
|
||||||
{{ info.posted ? 'Posted' : 'Post' }}
|
{{ info().posted ? 'Posted' : 'Post' }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-raised-button color="warn" (click)="confirmDelete()" [disabled]="!canSave()">Delete</button>
|
<button mat-raised-button color="warn" (click)="confirmDelete()" [disabled]="!canSave()">Delete</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CurrencyPipe, DecimalPipe } from '@angular/common';
|
import { CurrencyPipe, DecimalPipe } from '@angular/common';
|
||||||
import { Component, ElementRef, HostListener, inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, HostListener, inject, computed, input, linkedSignal, signal, afterNextRender } from '@angular/core';
|
||||||
import { FormArray, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatOptionModule } from '@angular/material/core';
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
@@ -8,31 +8,40 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||||
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
import { CostCentre } from '../core/cost-centre';
|
import { CostCentre } from '../core/cost-centre';
|
||||||
import { User } from '../core/user';
|
import { User } from '../core/user';
|
||||||
|
import { CostCentreService } from '../cost-centre/cost-centre.service';
|
||||||
import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component';
|
import { ConfirmDialogComponent } from '../shared/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||||
|
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||||
import { ToCsvService } from '../shared/to-csv.service';
|
import { ToCsvService } from '../shared/to-csv.service';
|
||||||
import { ClosingStock } from './closing-stock';
|
import { ClosingStock } from './closing-stock';
|
||||||
import { ClosingStockDataSource } from './closing-stock-datasource';
|
|
||||||
import { ClosingStockItem } from './closing-stock-item';
|
import { ClosingStockItem } from './closing-stock-item';
|
||||||
import { ClosingStockService } from './closing-stock.service';
|
import { ClosingStockService } from './closing-stock.service';
|
||||||
|
|
||||||
|
export interface ClosingStockFormData {
|
||||||
|
date: moment.Moment;
|
||||||
|
costCentre: CostCentre | null;
|
||||||
|
stocks: { physical: number; costCentre: string | undefined }[];
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-closing-stock',
|
selector: 'app-closing-stock',
|
||||||
templateUrl: './closing-stock.component.html',
|
templateUrl: './closing-stock.component.html',
|
||||||
styleUrls: ['./closing-stock.component.css'],
|
styleUrls: ['./closing-stock.component.css'],
|
||||||
imports: [
|
imports: [
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
ReactiveFormsModule,
|
FormField,
|
||||||
|
FormRoot,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatOptionModule,
|
MatOptionModule,
|
||||||
@@ -44,75 +53,92 @@ import { ClosingStockService } from './closing-stock.service';
|
|||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
DecimalPipe,
|
DecimalPipe,
|
||||||
CurrencyPipe,
|
CurrencyPipe,
|
||||||
|
|
||||||
|
SkeletonLoaderComponent,
|
||||||
|
ErrorStateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ClosingStockComponent implements OnInit {
|
export class ClosingStockComponent {
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private toCsv = inject(ToCsvService);
|
private toCsv = inject(ToCsvService);
|
||||||
private dialog = inject(MatDialog);
|
private dialog = inject(MatDialog);
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
auth = inject(AuthService);
|
protected auth = inject(AuthService);
|
||||||
private ser = inject(ClosingStockService);
|
private ser = inject(ClosingStockService);
|
||||||
|
private costCentreSer = inject(CostCentreService);
|
||||||
|
pageSize = signal(50);
|
||||||
|
pageIndex = signal(0);
|
||||||
|
sortActive = signal('');
|
||||||
|
sortDirection = signal('');
|
||||||
|
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
d = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
startDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
finishDate = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||||
|
infoResource = this.ser.list(this.id, this.d);
|
||||||
|
info = computed(() => this.infoResource.value() ?? new ClosingStock());
|
||||||
|
costCentresResource = this.costCentreSer.list();
|
||||||
|
|
||||||
|
costCentres = computed(() => this.costCentresResource.value() ?? []);
|
||||||
|
|
||||||
|
model = linkedSignal({
|
||||||
|
source: computed(() => ({ info: this.info(), costCentres: this.costCentres() })),
|
||||||
|
computation: ({ info, costCentres }): ClosingStockFormData => ({
|
||||||
|
date: moment(info.date, 'DD-MMM-YYYY'),
|
||||||
|
costCentre: costCentres.find((c) => c.id === info.costCentre?.id) || null,
|
||||||
|
stocks: info.items.map((x) => ({
|
||||||
|
physical: x.physical,
|
||||||
|
costCentre: x.costCentre?.id,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
form = createForm(this.model);
|
||||||
|
|
||||||
|
sortedList = computed(() => {
|
||||||
|
const data = this.info().items ?? [];
|
||||||
|
const active = this.sortActive();
|
||||||
|
const direction = this.sortDirection();
|
||||||
|
|
||||||
|
if (!active || direction === '') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...data].sort((a, b) => {
|
||||||
|
const isAsc = direction === 'asc';
|
||||||
|
switch (active) {
|
||||||
|
case 'product':
|
||||||
|
return compare(a.product?.name ?? '', b.product?.name ?? '', isAsc);
|
||||||
|
case 'group':
|
||||||
|
return compare(a.group ?? '', b.group ?? '', isAsc);
|
||||||
|
case 'quantity':
|
||||||
|
return compare(a.quantity ?? 0, b.quantity ?? 0, isAsc);
|
||||||
|
case 'amount':
|
||||||
|
return compare(a.amount ?? 0, b.amount ?? 0, isAsc);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dataSource = computed(() => {
|
||||||
|
const data = this.sortedList();
|
||||||
|
const pageIndex = this.pageIndex();
|
||||||
|
const pageSize = this.pageSize();
|
||||||
|
return data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
|
||||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
|
||||||
@ViewChild('dateElement', { static: true }) dateElement!: ElementRef<HTMLInputElement>;
|
|
||||||
info: ClosingStock = new ClosingStock();
|
|
||||||
dataSource: ClosingStockDataSource = new ClosingStockDataSource(this.info.items, this.paginator, this.sort);
|
|
||||||
form: FormGroup<{
|
|
||||||
date: FormControl<moment.Moment>;
|
|
||||||
costCentre: FormControl<string | null>;
|
|
||||||
stocks: FormArray<
|
|
||||||
FormGroup<{
|
|
||||||
physical: FormControl<number>;
|
|
||||||
costCentre: FormControl<string | undefined>;
|
|
||||||
}>
|
|
||||||
>;
|
|
||||||
}>;
|
|
||||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||||
|
|
||||||
displayedColumns = ['product', 'group', 'quantity', 'physical', 'variance', 'department', 'amount'];
|
displayedColumns = ['product', 'group', 'quantity', 'physical', 'variance', 'department', 'amount'];
|
||||||
|
|
||||||
costCentres: CostCentre[];
|
|
||||||
|
|
||||||
@HostListener('window:keydown.f2', ['$event'])
|
@HostListener('window:keydown.f2', ['$event'])
|
||||||
focusDate(event: Event) {
|
focusDate(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.dateElement.nativeElement.focus();
|
this.form().focusBoundControl();
|
||||||
this.dateElement.nativeElement.select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.costCentres = [];
|
afterNextRender(() => {
|
||||||
this.form = new FormGroup({
|
this.form().focusBoundControl();
|
||||||
date: new FormControl(moment(new Date()), { nonNullable: true }),
|
|
||||||
costCentre: new FormControl<string | null>(null),
|
|
||||||
stocks: new FormArray<FormGroup<{ physical: FormControl<number>; costCentre: FormControl<string | undefined> }>>(
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.route.data.subscribe((value) => {
|
|
||||||
const data = value as { info: ClosingStock; costCentres: CostCentre[] };
|
|
||||||
this.info = data.info;
|
|
||||||
this.costCentres = data.costCentres;
|
|
||||||
this.form.patchValue({
|
|
||||||
date: moment(this.info.date, 'DD-MMM-YYYY'),
|
|
||||||
costCentre: this.info.costCentre.id,
|
|
||||||
});
|
|
||||||
this.form.controls.stocks.clear();
|
|
||||||
this.info.items.forEach((x) =>
|
|
||||||
this.form.controls.stocks.push(
|
|
||||||
new FormGroup({
|
|
||||||
physical: new FormControl(x.physical, { nonNullable: true }),
|
|
||||||
costCentre: new FormControl(x.costCentre?.id, { nonNullable: true }),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.dataSource = new ClosingStockDataSource(this.info.items, this.paginator, this.sort);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,25 +163,23 @@ export class ClosingStockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getClosingStock(): ClosingStock {
|
getClosingStock(): ClosingStock {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
this.info.date = moment(formModel.date).format('DD-MMM-YYYY');
|
const currentInfo = this.info();
|
||||||
const array = this.form.controls.stocks;
|
currentInfo.date = formModel.date.format('DD-MMM-YYYY');
|
||||||
this.info.items.forEach((item, index) => {
|
const array = formModel.stocks;
|
||||||
item.physical = +(array.controls[index].value.physical ?? '');
|
currentInfo.items.forEach((item, index) => {
|
||||||
item.costCentre =
|
item.physical = array[index].physical ?? 0;
|
||||||
array.controls[index].value.costCentre == null
|
item.costCentre = array[index].costCentre == null ? undefined : new CostCentre({ id: array[index].costCentre });
|
||||||
? undefined
|
|
||||||
: new CostCentre({ id: array.controls[index].value.costCentre });
|
|
||||||
});
|
});
|
||||||
return this.info;
|
return currentInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo(): ClosingStock {
|
getInfo(): ClosingStock {
|
||||||
const formModel = this.form.value;
|
const formModel = this.model();
|
||||||
|
|
||||||
return new ClosingStock({
|
return new ClosingStock({
|
||||||
date: moment(formModel.date).format('DD-MMM-YYYY'),
|
date: formModel.date.format('DD-MMM-YYYY'),
|
||||||
costCentre: new CostCentre({ id: formModel.costCentre ?? '' }),
|
costCentre: new CostCentre({ id: formModel.costCentre?.id ?? '' }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +191,7 @@ export class ClosingStockComponent implements OnInit {
|
|||||||
Amount: 'amount',
|
Amount: 'amount',
|
||||||
};
|
};
|
||||||
|
|
||||||
const d = JSON.parse(JSON.stringify(this.dataSource.data)).map((x: ClosingStockItem) => ({
|
const d = JSON.parse(JSON.stringify(this.dataSource())).map((x: ClosingStockItem) => ({
|
||||||
product: x.product.name,
|
product: x.product.name,
|
||||||
group: x.group,
|
group: x.group,
|
||||||
quantity: x.quantity,
|
quantity: x.quantity,
|
||||||
@@ -193,21 +217,21 @@ export class ClosingStockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canDelete() {
|
canDelete() {
|
||||||
return this.info.items.find((x) => !!x.id) !== undefined;
|
return this.info().items.find((x) => !!x.id) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
canSave() {
|
canSave() {
|
||||||
if (this.info.items.find((x) => !!x.id) !== undefined) {
|
if (this.info().items.find((x) => !!x.id) !== undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.info.posted && this.auth.allowed('edit-posted-vouchers')) {
|
if (this.info().posted && this.auth.allowed('edit-posted-vouchers')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.info.user.id === (this.auth.user as User).id || this.auth.allowed("edit-other-user's-vouchers");
|
return this.info().user.id === (this.auth.user as User).id || this.auth.allowed("edit-other-user's-vouchers");
|
||||||
}
|
}
|
||||||
|
|
||||||
post() {
|
post() {
|
||||||
this.ser.post(this.info.date, this.info.costCentre.id as string).subscribe({
|
this.ser.post(this.info().date, this.info().costCentre.id as string).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('Voucher Posted', 'Success');
|
this.snackBar.open('Voucher Posted', 'Success');
|
||||||
},
|
},
|
||||||
@@ -218,7 +242,7 @@ export class ClosingStockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
this.ser.delete(this.info.date, this.info.costCentre.id as string).subscribe({
|
this.ser.delete(this.info().date, this.info().costCentre.id as string).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.snackBar.open('', 'Success');
|
this.snackBar.open('', 'Success');
|
||||||
this.router.navigate(['/closing-stock'], { replaceUrl: true });
|
this.router.navigate(['/closing-stock'], { replaceUrl: true });
|
||||||
@@ -244,4 +268,16 @@ export class ClosingStockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePageEvent(e: PageEvent) {
|
||||||
|
this.pageSize.set(e.pageSize);
|
||||||
|
this.pageIndex.set(e.pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
this.sortActive.set(sort.active);
|
||||||
|
this.sortDirection.set(sort.direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const compare = (a: string | number | boolean, b: string | number | boolean, isAsc: boolean) =>
|
||||||
|
(a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { ClosingStock } from './closing-stock';
|
|
||||||
import { closingStockResolver } from './closing-stock.resolver';
|
|
||||||
|
|
||||||
describe('closingStockResolver', () => {
|
|
||||||
const executeResolver: ResolveFn<ClosingStock> = (...resolverParameters) =>
|
|
||||||
TestBed.runInInjectionContext(() => closingStockResolver(...resolverParameters));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(executeResolver).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { inject } from '@angular/core';
|
|
||||||
import { ResolveFn } from '@angular/router';
|
|
||||||
|
|
||||||
import { ClosingStock } from './closing-stock';
|
|
||||||
import { ClosingStockService } from './closing-stock.service';
|
|
||||||
|
|
||||||
export const closingStockResolver: ResolveFn<ClosingStock> = (route) => {
|
|
||||||
const date = route.paramMap.get('date');
|
|
||||||
const costCentre = route.queryParamMap.get('d') || null;
|
|
||||||
return inject(ClosingStockService).list(date, costCentre);
|
|
||||||
};
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
import { authGuard } from '../auth/auth-guard.service';
|
import { authGuard } from '../auth/auth-guard.service';
|
||||||
import { costCentreListResolver } from '../cost-centre/cost-centre-list.resolver';
|
|
||||||
import { ClosingStockComponent } from './closing-stock.component';
|
import { ClosingStockComponent } from './closing-stock.component';
|
||||||
import { closingStockResolver } from './closing-stock.resolver';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -13,10 +11,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Closing Stock',
|
permission: 'Closing Stock',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: closingStockResolver,
|
|
||||||
costCentres: costCentreListResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -26,10 +20,6 @@ export const routes: Routes = [
|
|||||||
data: {
|
data: {
|
||||||
permission: 'Closing Stock',
|
permission: 'Closing Stock',
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
info: closingStockResolver,
|
|
||||||
costCentres: costCentreListResolver,
|
|
||||||
},
|
|
||||||
runGuardsAndResolvers: 'always',
|
runGuardsAndResolvers: 'always',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, httpResource, HttpParams } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -7,8 +7,6 @@ import { ErrorLoggerService } from '../core/error-logger.service';
|
|||||||
import { ClosingStock } from './closing-stock';
|
import { ClosingStock } from './closing-stock';
|
||||||
|
|
||||||
const url = '/api/closing-stock';
|
const url = '/api/closing-stock';
|
||||||
const serviceName = 'ClosingStockService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -16,34 +14,33 @@ export class ClosingStockService {
|
|||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
list(date: string | null, costCentre: string | null): Observable<ClosingStock> {
|
list(date: Signal<string | null>, costCentre: Signal<string | null>) {
|
||||||
const listUrl = date === null ? url : `${url}/${date}`;
|
return httpResource<ClosingStock>(() => {
|
||||||
const options = { params: new HttpParams() };
|
const listUrl = date() === null ? url : `${url}/${date()}`;
|
||||||
if (costCentre !== null) {
|
const params: Record<string, string> = {};
|
||||||
options.params = options.params.set('d', costCentre);
|
const costCentre_val = costCentre();
|
||||||
}
|
if (costCentre_val !== null) params['d'] = costCentre_val;
|
||||||
return this.http
|
return { url: listUrl, params };
|
||||||
.get<ClosingStock>(listUrl, options)
|
});
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<ClosingStock>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save(closingStock: ClosingStock): Observable<ClosingStock> {
|
save(closingStock: ClosingStock): Observable<ClosingStock> {
|
||||||
return this.http
|
return this.http
|
||||||
.post<ClosingStock>(url, closingStock)
|
.post<ClosingStock>(url, closingStock)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<ClosingStock>;
|
.pipe(catchError(this.log.handleError('ClosingStockService', 'save'))) as Observable<ClosingStock>;
|
||||||
}
|
}
|
||||||
|
|
||||||
post(date: string, costCentre: string): Observable<ClosingStock> {
|
post(date: string, costCentre: string): Observable<ClosingStock> {
|
||||||
const options = { params: new HttpParams().set('d', costCentre) };
|
const options = { params: new HttpParams().set('d', costCentre) };
|
||||||
return this.http
|
return this.http
|
||||||
.post<ClosingStock>(`${url}/${date}`, {}, options)
|
.post<ClosingStock>(`${url}/${date}`, {}, options)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Post Voucher'))) as Observable<ClosingStock>;
|
.pipe(catchError(this.log.handleError('ClosingStockService', 'Post Voucher'))) as Observable<ClosingStock>;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(date: string, costCentre: string): Observable<ClosingStock> {
|
delete(date: string, costCentre: string): Observable<ClosingStock> {
|
||||||
const options = { params: new HttpParams().set('d', costCentre) };
|
const options = { params: new HttpParams().set('d', costCentre) };
|
||||||
return this.http
|
return this.http
|
||||||
.delete<ClosingStock>(`${url}/${date}`, options)
|
.delete<ClosingStock>(`${url}/${date}`, options)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Delete Voucher'))) as Observable<ClosingStock>;
|
.pipe(catchError(this.log.handleError('ClosingStockService', 'Delete Voucher'))) as Observable<ClosingStock>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -8,50 +8,40 @@ import { AccountBalance } from './account-balance';
|
|||||||
import { ErrorLoggerService } from './error-logger.service';
|
import { ErrorLoggerService } from './error-logger.service';
|
||||||
|
|
||||||
const url = '/api/accounts';
|
const url = '/api/accounts';
|
||||||
const serviceName = 'AccountService';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
get(id: string | null): Observable<Account> {
|
get(id: Signal<string | null>) {
|
||||||
const getUrl: string = id === null ? `${url}` : `${url}/${id}`;
|
return httpResource<Account>(() => {
|
||||||
return this.http
|
const getUrl: string = id() === null ? `${url}` : `${url}/${id()}`;
|
||||||
.get<Account>(getUrl)
|
return getUrl;
|
||||||
.pipe(catchError(this.log.handleError(serviceName, `get id=${id}`))) as Observable<Account>;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
list(): Observable<Account[]> {
|
list() {
|
||||||
return this.http
|
return httpResource<Account[]>(() => `${url}/list`);
|
||||||
.get<Account[]>(`${url}/list`)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Account[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paymentAutocomplete(query: string): Observable<Account[]> {
|
paymentAutocomplete(query: Signal<string>) {
|
||||||
const options = { params: new HttpParams().set('q', query).set('t', '1') };
|
return httpResource<Account[]>(() => ({ url: `${url}/query`, params: { q: query(), t: '1' } }));
|
||||||
return this.http
|
|
||||||
.get<Account[]>(`${url}/query`, options)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Account[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptAutocomplete(query: string): Observable<Account[]> {
|
receiptAutocomplete(query: Signal<string>) {
|
||||||
const options = { params: new HttpParams().set('q', query).set('t', '1') };
|
return httpResource<Account[]>(() => ({ url: `${url}/query`, params: { q: query(), t: '1' } }));
|
||||||
return this.http
|
|
||||||
.get<Account[]>(`${url}/query`, options)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'list'))) as Observable<Account[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save(account: Account): Observable<null> {
|
save(account: Account): Observable<null> {
|
||||||
return this.http
|
return this.http
|
||||||
.post<null>(`${url}`, account)
|
.post<null>(`${url}`, account)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'save'))) as Observable<null>;
|
.pipe(catchError(this.log.handleError('AccountService', 'save'))) as Observable<null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(account: Account): Observable<null> {
|
update(account: Account): Observable<null> {
|
||||||
return this.http
|
return this.http
|
||||||
.put<null>(`${url}/${account.id}`, account)
|
.put<null>(`${url}/${account.id}`, account)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'update'))) as Observable<null>;
|
.pipe(catchError(this.log.handleError('AccountService', 'update'))) as Observable<null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOrUpdate(account: Account): Observable<null> {
|
saveOrUpdate(account: Account): Observable<null> {
|
||||||
@@ -64,20 +54,22 @@ export class AccountService {
|
|||||||
delete(id: string): Observable<Account> {
|
delete(id: string): Observable<Account> {
|
||||||
return this.http
|
return this.http
|
||||||
.delete<Account>(`${url}/${id}`)
|
.delete<Account>(`${url}/${id}`)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<Account>;
|
.pipe(catchError(this.log.handleError('AccountService', 'delete'))) as Observable<Account>;
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete(query: string): Observable<Account[]> {
|
autocomplete(query: Signal<string | null>) {
|
||||||
const options = { params: new HttpParams().set('q', query) };
|
return httpResource<Account[]>(() => {
|
||||||
return this.http
|
const q = query();
|
||||||
.get<Account[]>(`${url}/query`, options)
|
return q ? `${url}/query?q=${encodeURIComponent(q)}` : undefined;
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable<Account[]>;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
balance(id: string, date: string): Observable<AccountBalance> {
|
balance(id: Signal<string | null>, date: Signal<string | null>) {
|
||||||
const options = { params: new HttpParams().set('d', date) };
|
return httpResource<AccountBalance>(() => {
|
||||||
return this.http
|
const accId = id();
|
||||||
.get<AccountBalance>(`${url}/${id}/balance`, options)
|
const d = date();
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'balance'))) as Observable<AccountBalance>;
|
if (!accId || !d) return undefined;
|
||||||
|
return `${url}/${accId}/balance?d=${encodeURIComponent(d)}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, httpResource, HttpParams } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { Batch } from './batch';
|
import { Batch } from './batch';
|
||||||
import { ErrorLoggerService } from './error-logger.service';
|
import { ErrorLoggerService } from './error-logger.service';
|
||||||
|
|
||||||
const url = '/api/batch';
|
const url = '/api/batch';
|
||||||
const serviceName = 'BatchService';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class BatchService {
|
export class BatchService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private log = inject(ErrorLoggerService);
|
private log = inject(ErrorLoggerService);
|
||||||
|
|
||||||
autocomplete(date: string, term: string): Observable<Batch[]> {
|
autocomplete(date: Signal<string | null>, term: Signal<string | null>) {
|
||||||
const options = { params: new HttpParams().set('q', term).set('d', date) };
|
return httpResource<Batch[]>(() => {
|
||||||
return this.http
|
const q = term();
|
||||||
.get<Batch[]>(url, options)
|
const d = date();
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable<Batch[]>;
|
if (!q || !d) return undefined;
|
||||||
|
return { url, params: new HttpParams().set('q', q).set('d', d) };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { HttpInterceptorFn } from '@angular/common/http';
|
||||||
|
import { timer, switchMap } from 'rxjs';
|
||||||
|
|
||||||
|
export const delayInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
|
// Delay all requests by 1500ms to test loading states
|
||||||
|
return timer(1500).pipe(switchMap(() => next(req)));
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<mat-toolbar class="fixed-to-top row-container">
|
<mat-toolbar class="fixed-to-top row-container">
|
||||||
<span class=""
|
<span class=""
|
||||||
><a routerLink="/">{{ title }}</a></span
|
><a routerLink="/">{{ title() }}</a></span
|
||||||
>
|
>
|
||||||
<mat-menu #voucherMenu="matMenu">
|
<mat-menu #voucherMenu="matMenu">
|
||||||
<a mat-menu-item routerLink="/journal">Journal - F7</a>
|
<a mat-menu-item routerLink="/journal">Journal - F7</a>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import { AsyncPipe } from '@angular/common';
|
||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, computed } from '@angular/core';
|
||||||
import { MatAnchor, MatButton } from '@angular/material/button';
|
import { MatAnchor, MatButton } from '@angular/material/button';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
||||||
@@ -16,22 +16,13 @@ import { TitleService } from '../title.service';
|
|||||||
styleUrls: ['./nav-bar.component.css'],
|
styleUrls: ['./nav-bar.component.css'],
|
||||||
imports: [MatToolbar, RouterLink, MatMenu, MatMenuItem, MatButton, MatMenuTrigger, MatIcon, MatAnchor, AsyncPipe],
|
imports: [MatToolbar, RouterLink, MatMenu, MatMenuItem, MatButton, MatMenuTrigger, MatIcon, MatAnchor, AsyncPipe],
|
||||||
})
|
})
|
||||||
export class NavBarComponent implements OnInit {
|
export class NavBarComponent {
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
auth = inject(AuthService);
|
auth = inject(AuthService);
|
||||||
titleService = inject(TitleService);
|
titleService = inject(TitleService);
|
||||||
|
|
||||||
title: string;
|
private titleResource = this.titleService.get();
|
||||||
|
title = computed(() => this.titleResource.value() ?? environment.title);
|
||||||
constructor() {
|
|
||||||
this.title = environment.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.titleService.get().subscribe((value) => {
|
|
||||||
this.title = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.auth.logout();
|
this.auth.logout();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Product } from './product';
|
||||||
|
|
||||||
export class ProductSku {
|
export class ProductSku {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -6,6 +8,8 @@ export class ProductSku {
|
|||||||
fractionUnits: string;
|
fractionUnits: string;
|
||||||
|
|
||||||
isRateContracted: boolean;
|
isRateContracted: boolean;
|
||||||
|
stock?: number;
|
||||||
|
product?: Product;
|
||||||
|
|
||||||
public constructor(init?: Partial<ProductSku>) {
|
public constructor(init?: Partial<ProductSku>) {
|
||||||
this.id = '';
|
this.id = '';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ProductGroup } from './product-group';
|
import { ProductGroup } from './product-group';
|
||||||
|
|
||||||
export class StockKeepingUnit {
|
export class StockKeepingUnit {
|
||||||
|
id: string | null;
|
||||||
units: string;
|
units: string;
|
||||||
fraction: number;
|
fraction: number;
|
||||||
productYield: number;
|
productYield: number;
|
||||||
@@ -8,6 +9,7 @@ export class StockKeepingUnit {
|
|||||||
salePrice: number;
|
salePrice: number;
|
||||||
|
|
||||||
public constructor(init?: Partial<StockKeepingUnit>) {
|
public constructor(init?: Partial<StockKeepingUnit>) {
|
||||||
|
this.id = null;
|
||||||
this.units = '';
|
this.units = '';
|
||||||
this.fraction = 1;
|
this.fraction = 1;
|
||||||
this.productYield = 1;
|
this.productYield = 1;
|
||||||
@@ -23,6 +25,7 @@ export class Product {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string | undefined;
|
description: string | undefined;
|
||||||
skus: StockKeepingUnit[];
|
skus: StockKeepingUnit[];
|
||||||
|
unit?: { name: string };
|
||||||
fractionUnits: string | undefined;
|
fractionUnits: string | undefined;
|
||||||
|
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { catchError } from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { ErrorLoggerService } from './error-logger.service';
|
|
||||||
|
|
||||||
const url = '/api/title';
|
const url = '/api/title';
|
||||||
const serviceName = 'VoucherService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class TitleService {
|
export class TitleService {
|
||||||
private http = inject(HttpClient);
|
get() {
|
||||||
private log = inject(ErrorLoggerService);
|
return httpResource<string>(() => url);
|
||||||
|
|
||||||
get(): Observable<string> {
|
|
||||||
return this.http
|
|
||||||
.get<string>(url)
|
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Get Title'))) as Observable<string>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, httpResource } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable, Signal, ResourceRef } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -7,8 +7,6 @@ import { ErrorLoggerService } from './error-logger.service';
|
|||||||
import { Voucher } from './voucher';
|
import { Voucher } from './voucher';
|
||||||
|
|
||||||
const url = '/api';
|
const url = '/api';
|
||||||
const serviceName = 'VoucherService';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@@ -31,46 +29,64 @@ export class VoucherService {
|
|||||||
return new Blob([ab], { type: mimeString });
|
return new Blob([ab], { type: mimeString });
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id: string, type: string): Observable<Voucher> {
|
get(id: Signal<string | undefined>, type: Signal<string>): ResourceRef<Voucher | undefined> {
|
||||||
const endpoint = type.replace(/ /g, '-').toLowerCase();
|
return httpResource<Voucher>(() => {
|
||||||
return this.http
|
const i = id();
|
||||||
.get<Voucher>(`${url}/${endpoint}/${id}`)
|
if (!i) return undefined;
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Get Voucher'))) as Observable<Voucher>;
|
const endpoint = type().replace(/ /g, '-').toLowerCase();
|
||||||
|
return `${url}/${endpoint}/${i}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getOfType(type: string, account: string | null, date: string | null): Observable<Voucher> {
|
getOfType(
|
||||||
const endpoint = type.replace(/ /g, '-').toLowerCase();
|
type: Signal<string>,
|
||||||
const options = {
|
account: Signal<string | null>,
|
||||||
params: new HttpParams(),
|
date: Signal<string | null>,
|
||||||
};
|
): ResourceRef<Voucher | undefined> {
|
||||||
if (account !== null) {
|
return httpResource<Voucher>(() => {
|
||||||
options.params = options.params.set('a', account);
|
const endpoint = type().replace(/ /g, '-').toLowerCase();
|
||||||
}
|
const params = new URLSearchParams();
|
||||||
if (date !== null) {
|
if (account() !== null) params.set('a', account()!);
|
||||||
options.params = options.params.set('d', date);
|
if (date() !== null) params.set('d', date()!);
|
||||||
}
|
const p = params.toString();
|
||||||
return this.http
|
return p ? `${url}/${endpoint}?${p}` : `${url}/${endpoint}`;
|
||||||
.get<Voucher>(`${url}/${endpoint}`, options)
|
});
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Get Voucher of Type'))) as Observable<Voucher>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getIncentive(date: string): Observable<Voucher> {
|
getVoucher(
|
||||||
const options = { params: new HttpParams().set('d', date) };
|
id: Signal<string | null>,
|
||||||
return this.http
|
type: Signal<string>,
|
||||||
.get<Voucher>(`${url}/incentive`, options)
|
account: Signal<string | null>,
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Get Incentive'))) as Observable<Voucher>;
|
date: Signal<string | null>,
|
||||||
|
): ResourceRef<Voucher | undefined> {
|
||||||
|
return httpResource<Voucher>(() => {
|
||||||
|
const endpoint = type().replace(/ /g, '-').toLowerCase();
|
||||||
|
const i = id();
|
||||||
|
if (i) {
|
||||||
|
return `${url}/${endpoint}/${i}`;
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (account() !== null) params.set('a', account()!);
|
||||||
|
if (date() !== null) params.set('d', date()!);
|
||||||
|
const p = params.toString();
|
||||||
|
return p ? `${url}/${endpoint}?${p}` : `${url}/${endpoint}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getIncentive(date: Signal<string>): ResourceRef<Voucher | undefined> {
|
||||||
|
return httpResource<Voucher>(() => `${url}/incentive?d=${date()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
post(id: string): Observable<Voucher> {
|
post(id: string): Observable<Voucher> {
|
||||||
return this.http
|
return this.http
|
||||||
.post<Voucher>(`${url}/post-voucher/${id}`, {})
|
.post<Voucher>(`${url}/post-voucher/${id}`, {})
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Post Voucher'))) as Observable<Voucher>;
|
.pipe(catchError(this.log.handleError('VoucherService', 'Post Voucher'))) as Observable<Voucher>;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(id: string): Observable<Voucher> {
|
delete(id: string): Observable<Voucher> {
|
||||||
return this.http
|
return this.http
|
||||||
.delete<Voucher>(`${url}/delete/${id}`)
|
.delete<Voucher>(`${url}/delete/${id}`)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Delete Voucher'))) as Observable<Voucher>;
|
.pipe(catchError(this.log.handleError('VoucherService', 'Delete Voucher'))) as Observable<Voucher>;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveOrUpdate(voucher: Voucher): Observable<Voucher> {
|
saveOrUpdate(voucher: Voucher): Observable<Voucher> {
|
||||||
@@ -93,7 +109,7 @@ export class VoucherService {
|
|||||||
fd.append('data', JSON.stringify(voucher));
|
fd.append('data', JSON.stringify(voucher));
|
||||||
return this.http
|
return this.http
|
||||||
.post<Voucher>(`${url}/${endpoint}`, fd)
|
.post<Voucher>(`${url}/${endpoint}`, fd)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Save Voucher'))) as Observable<Voucher>;
|
.pipe(catchError(this.log.handleError('VoucherService', 'Save Voucher'))) as Observable<Voucher>;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(voucher: Voucher, endpoint: string): Observable<Voucher> {
|
update(voucher: Voucher, endpoint: string): Observable<Voucher> {
|
||||||
@@ -108,6 +124,6 @@ export class VoucherService {
|
|||||||
fd.append('data', JSON.stringify(voucher));
|
fd.append('data', JSON.stringify(voucher));
|
||||||
return this.http
|
return this.http
|
||||||
.put<Voucher>(`${url}/${endpoint}/${voucher.id}`, fd)
|
.put<Voucher>(`${url}/${endpoint}/${voucher.id}`, fd)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'Update Voucher'))) as Observable<Voucher>;
|
.pipe(catchError(this.log.handleError('VoucherService', 'Update Voucher'))) as Observable<Voucher>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,21 @@
|
|||||||
<h2>Cost Center</h2>
|
<h2>Cost Center</h2>
|
||||||
|
|
||||||
<form [formGroup]="form" class="flex-col wrapped">
|
@if (itemResource.isLoading()) {
|
||||||
|
<app-skeleton-loader type="card" [count]="1" />
|
||||||
|
<app-skeleton-loader type="line" [count]="10" />
|
||||||
|
} @else if (itemResource.error()) {
|
||||||
|
<app-error-state [message]="'Failed to load data.'" (retryAction)="itemResource.reload()" />
|
||||||
|
} @else {
|
||||||
|
<form class="flex-col wrapped" [formRoot]="form">
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<mat-form-field class="flex-auto">
|
<mat-form-field class="flex-auto">
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input matInput #nameElement formControlName="name" (keyup.enter)="save()" />
|
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="row-container">
|
<div class="row-container">
|
||||||
<button mat-raised-button (click)="save()" color="primary">Save</button>
|
<button mat-raised-button (click)="save()" color="primary">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
Loaded 100 of 443 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user