From 32c068895180bdd488de92e5e83d70e3435a8712 Mon Sep 17 00:00:00 2001 From: lewis_yan Date: Thu, 2 Apr 2026 09:22:02 +0800 Subject: [PATCH] chore: add .planning and docs/superpowers to .gitignore Remove tracked planning files from git index. --- .gitignore | 9 +- .planning/codebase/ARCHITECTURE.md | 208 ---- .planning/codebase/CONCERNS.md | 248 ---- .planning/codebase/CONVENTIONS.md | 316 ----- .planning/codebase/INTEGRATIONS.md | 295 ----- .planning/codebase/STACK.md | 227 ---- .planning/codebase/STRUCTURE.md | 247 ---- .planning/codebase/TESTING.md | 238 ---- .../2026-04-01-dashboard-redesign-plan.md | 1085 ----------------- .../2026-04-01-dashboard-redesign-design.md | 267 ---- 10 files changed, 8 insertions(+), 3132 deletions(-) delete mode 100644 .planning/codebase/ARCHITECTURE.md delete mode 100644 .planning/codebase/CONCERNS.md delete mode 100644 .planning/codebase/CONVENTIONS.md delete mode 100644 .planning/codebase/INTEGRATIONS.md delete mode 100644 .planning/codebase/STACK.md delete mode 100644 .planning/codebase/STRUCTURE.md delete mode 100644 .planning/codebase/TESTING.md delete mode 100644 docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md delete mode 100644 docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md diff --git a/.gitignore b/.gitignore index 12bc2b3..d5e0305 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,11 @@ .vscode/ web/package-lock.json -*.bat \ No newline at end of file +*.bat + +# GSD planning files +.planning/ + +# Superpowers files +docs/superpowers/ +.pids/ \ No newline at end of file diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md deleted file mode 100644 index 52e8bf6..0000000 --- a/.planning/codebase/ARCHITECTURE.md +++ /dev/null @@ -1,208 +0,0 @@ -# Architecture - -**Analysis Date:** 2026-04-01 - -## Pattern Overview - -**Overall:** Django REST Framework backend with Vue 3 SPA frontend, built on django-vue3-admin base - -**Key Characteristics:** -- Backend uses Django 4.2 with DRF for API-first design -- Frontend is Vue 3 + TypeScript + Vite SPA -- JWT authentication via SimpleJWT -- Real-time support via Django Channels (WebSocket) -- Backend-controlled routing (menu/permissions served via API) -- Custom apps extend dvadmin base RBAC system - -## Layers - -**Django Application Layer:** -- Purpose: Main Django project configuration and URL routing -- Location: `backend/application/` -- Contains: `settings.py` (imports config from `conf/env.py`), `urls.py`, `asgi.py`, `wsgi.py`, `celery.py` -- Depends on: All Django apps -- Used by: uvicorn/ASGI server - -**Custom Business Apps Layer (pisadmin, pissupplier):** -- Purpose: Procurement and supplier quotation business logic -- Location: `backend/apps/pisadmin/` and `backend/apps/pissupplier/` -- Contains: Models, Views (ViewSets), Serializers, URLs for procurement RFQ, cost templates, suppliers -- Depends on: dvadmin utils, Django REST Framework -- Used by: Frontend SPA via REST API - -**dvadmin Core Layer:** -- Purpose: Base RBAC system - users, roles, menus, departments, permissions -- Location: `backend/dvadmin/system/` -- Contains: Auth models (Users, Roles, Menus), views, serializers -- Depends on: Django contrib apps -- Used by: Custom apps, frontend - -**dvadmin Utils Layer:** -- Purpose: Reusable utilities - CustomModelViewSet, filters, permissions, pagination, exception handling -- Location: `backend/dvadmin/utils/` -- Contains: `viewset.py` (CustomModelViewSet base), `filters.py`, `permission.py`, `pagination.py`, `serializers.py` -- Depends on: Django REST Framework -- Used by: All ViewSets across apps - -**Frontend SPA Layer:** -- Purpose: Vue 3 single-page application for UI -- Location: `web/src/` -- Contains: Components, views, stores (Pinia), router, API clients -- Depends on: Vue 3, Element Plus, FastCRUD, Pinia, Vue Router - -## Data Flow - -**Frontend-to-Backend API Flow:** - -``` -Vue Component (Page) - -> Pinia Store (State Management) - -> API Client (src/api/*/index.ts) - -> HTTP Request (Axios wrapper) - -> Django REST Framework ViewSet - -> Django Serializer (Validation) - -> Django Model (Database) - -> Database Response - -> Serializer Response - -> JSON API Response - -> Pinia Store Update - -> Vue Component Re-render -``` - -**Authentication Flow:** - -``` -Login Page -> POST /api/login/ -> JWT Token Response - -> Token stored in Session storage - -> All subsequent requests include JWT header - -> Backend validates JWT, attaches user to request - -> Permission classes check role/menu permissions -``` - -**Menu/Routing Flow (Backend-Controlled):** - -``` -User Login -> GET /api/system/get_menu/ - -> Returns user-specific menu tree from Menu model - -> Frontend builds routes dynamically - -> Cached in frontendMenu store -``` - -**WebSocket Flow:** - -``` -Frontend connects to /ws// - -> Django Channels AuthMiddlewareStack validates JWT - -> URLRouter routes to MegCenter consumer - -> Consumer handles real-time messaging -``` - -## Key Design Patterns - -**CustomModelViewSet Pattern:** -All custom ViewSets inherit from `dvadmin.utils.viewset.CustomModelViewSet` which extends DRF's `ModelViewSet`. This provides: -- Standardized JSON responses (`SuccessResponse`, `DetailResponse`, `ErrorResponse`) -- Automatic filtering with `DataLevelPermissionMargeFilter` (data scope permissions) -- Field-level permissions via `FieldPermission` model -- Import/export mixins -- Custom serializer selection per action (`create_serializer_class`, `update_serializer_class`) - -**CoreModel Pattern:** -All models inherit from `dvadmin.utils.models.CoreModel` which provides: -- Audit fields: `creator`, `modifier`, `dept_belong_id`, `create_datetime`, `update_datetime` -- Auto-setting creator/modifier on create/update -- `SoftDeleteModel` support for soft deletes - -**Status Workflow Pattern (Inquiry/RFQ):** -The `Inquiry` model uses a status field with workflow transitions: -``` -1(开立) -> 2(确认) -> 3(发布) -> 4(报价中) -> 5(报价结束) - -> 6(比议价中) -> 7(价格审核) -> 8(核价通过) -9(落标结束) or 0(作废) -``` -Each transition is handled by a dedicated `@action` method on the ViewSet. - -**Template-Generated Quotation Pattern:** -- `CostEstimateTemplateHead` + `CostEstimateTemplateBody` define a cost structure template -- When Inquiry is published, `_create_supplier_quotations` iterates the template to generate `QuotationMaster` + child tables for each supplier -- Template fields with `is_computed=1` or `supplier_required in (1,2)` are prefilled in quotations - -## Module Responsibilities - -**pisadmin.miscprocurement:** -- `Inquiry`: RFQ (Request for Quotation) master with workflow states -- `InquirySupplier`: Supplier invite list per RFQ -- `InquiryMaterialCost`, `InquiryProcessCost`, `InquiryOtherCost`, `InquiryProfitCost`: Cost breakdown per RFQ line item -- `InquiryRfqItem`: Upper-level material items for the RFQ -- `CostEstimateTemplateHead/Body`: Cost template structure definition -- `MiscLowPriceHeader/Detail`: Comparison price records (lowest price tracking) -- `MiscNegotiationRecords`: Bargaining records -- `RFQOperationLogs`: Audit trail for RFQ operations - -**pisadmin.basicinfo:** -- `Company`: Company/factory information -- `Currency`: Currency and tax rate configuration -- `Supplier`: Supplier master data -- `SupplierUser`: Supplier user accounts -- `Unit`: Measurement units -- `SystemNoRule`: Document numbering rules (e.g., RFQ numbers, quotation numbers) -- `EmailNotice`: Email notification queue - -**pissupplier:** -- `QuotationMaster`: Supplier quotation header -- `QuotationMaterial`, `QuotationProcess`, `QuotationOther`, `QuotationProfit`: Quotation cost lines -- `QuotationItem`: Upper-level product items in quotation -- `QuotationAttachment`: Quotation attachments - -**dvadmin.system:** -- `Users`: User accounts with role/dept associations -- `Role`: Role definitions with permission keys -- `Menu`: Menu tree for UI and permission routing -- `MenuButton`: Button-level permissions on menus -- `MenuField`: Field-level permissions per model -- `Dept`: Department hierarchy -- `FieldPermission`: Role-field permission matrix -- `RoleMenuPermission`, `RoleMenuButtonPermission`: Role-menu assignments -- `OperationLog`, `LoginLog`: Audit logging -- `SystemConfig`: System configuration key-value store -- `Dictionary`: Dropdown value definitions -- `FileList`: File upload management -- `ApiWhiteList`: Public API endpoints bypassing auth - -## Error Handling - -**Backend Exception Handling:** -- Custom exception handler: `dvadmin.utils.exception.CustomExceptionHandler` -- DRF exceptions caught and formatted to standard JSON response -- Validation errors return 400 with field-specific messages - -**Frontend Error Handling:** -- Axios interceptor catches HTTP errors -- 401 redirects to login -- 403 shows permission denied -- 500 shows generic error message -- API errors display returned `msg` field - -## Cross-Cutting Concerns - -**Authentication:** SimpleJWT with 24-hour access tokens, 1-day refresh tokens. `CustomBackend` allows username OR email login. - -**Authorization:** -- Menu-level via `Menu` model and role assignments -- Button-level via `MenuButton` and `RoleMenuButtonPermission` -- Field-level via `MenuField` and `FieldPermission` -- Data scope via `DataLevelPermissionMargeFilter` (dept-based filtering) - -**Logging:** -- API access logged via `ApiLoggingMiddleware` -- Operation logs stored in `OperationLog` model -- RFQ operations logged to `RFQOperationLogs` - -**Validation:** -- Serializer-level validation in DRF -- Custom validators in `dvadmin.utils.validator` -- Business logic validation in ViewSet methods (e.g., workflow state checks) - ---- - -*Architecture analysis: 2026-04-01* diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md deleted file mode 100644 index f81ab46..0000000 --- a/.planning/codebase/CONCERNS.md +++ /dev/null @@ -1,248 +0,0 @@ -# Codebase Concerns - -**Analysis Date:** 2026-04-01 - -## Security Concerns - -### Hardcoded Django SECRET_KEY - -**Severity:** Critical - -- **File:** `backend/application/settings.py:31` -- **Issue:** Django SECRET_KEY is hardcoded in source code: `"django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm"` -- **Impact:** If this code is committed to version control or accessible externally, attackers can forge session cookies, CSRF tokens, and other signed data. -- **Recommendation:** Move SECRET_KEY to `conf/env.py` and load from environment variable. Generate a new key for production. - -### DEBUG Mode Enabled in Production Configuration - -**Severity:** High - -- **File:** `backend/conf/env.py:39` -- **Issue:** `DEBUG = True` is set in the actual environment configuration file -- **Impact:** This enables Swagger UI, detailed error pages, and sets `permission_classes = [permissions.AllowAny]` on schema views (`backend/application/urls.py:45`). It also bypasses authentication requirements for API documentation. -- **Recommendation:** Set `DEBUG = False` in `conf/env.py` for production deployments. The code already has conditional logic to handle this. - -### ALLOWED_HOSTS = ["*"] - -**Severity:** High - -- **Files:** `backend/conf/env.py:48`, `backend/conf/env.example.py:48`, `backend/application/settings.py:44` -- **Issue:** Backend accepts requests from any host -- **Impact:** Allows host header attacks, including cache poisoning and CSS injection. -- **Recommendation:** Specify explicit hosts in `ALLOWED_HOSTS` for production. - -### CORS Allows All Origins - -**Severity:** High - -- **File:** `backend/application/settings.py:179-181` -- **Issue:** - ```python - CORS_ORIGIN_ALLOW_ALL = True - CORS_ALLOW_CREDENTIALS = True - ``` -- **Impact:** Combined with `AllowAny` permission classes, any website can make authenticated requests to the API, potentially leaking user data. -- **Recommendation:** Configure `CORS_ALLOWED_ORIGINS` with explicit allowed origins. - -### Weak Password Hashing (MD5 Fallback) - -**Severity:** Critical - -- **Files:** - - `backend/dvadmin/utils/backends.py:30-33` - - `backend/dvadmin/system/models.py:91` - - `backend/dvadmin/system/views/login.py:275` - -- **Issue:** Authentication backend falls back to MD5 hashing when PBKDF2 verification fails: - ```python - verify_password = check_password(password, user.password) - if not verify_password: - password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest() - verify_password = check_password(password, user.password) - ``` - The `set_password` method also uses MD5: - ```python - def set_password(self, raw_password): - if raw_password: - super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest()) - ``` - -- **Impact:** MD5 is cryptographically broken and provides no protection against rainbow table attacks. User passwords in the database are effectively stored in plaintext. -- **Recommendation:** Remove MD5 fallback entirely. Migrate existing MD5-stored passwords to proper bcrypt/PBKDF2 hashing on next login. - -### API Endpoint Allows Unauthenticated Access in DEBUG - -**Severity:** Medium - -- **File:** `backend/application/urls.py:45` -- **Issue:** Swagger schema view permission depends on DEBUG flag: - ```python - permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ] - ``` -- **Impact:** Since DEBUG is True, API documentation is publicly accessible, potentially revealing endpoint structure. -- **Recommendation:** Disable DEBUG or ensure permission classes are properly set regardless of DEBUG. - -### Exception Handler Exposes Stack Traces - -**Severity:** Medium - -- **File:** `backend/dvadmin/utils/exception.py:113` -- **Issue:** - ```python - elif isinstance(ex, Exception): - logger.exception(traceback.format_exc()) - msg = str(ex) - ``` -- **Impact:** In DEBUG mode, raw exception messages can reveal internal implementation details. -- **Recommendation:** Return generic error messages in production. Current implementation logs but still exposes `str(ex)`. - -## Code Quality Concerns - -### Debug Print Statements in Production Code - -**Severity:** Low - -- **Files:** Multiple files contain print statements: - - `backend/application/dispatch.py:87, 110` - "请先进行数据库迁移!" - - `backend/util/currency.py:41, 57, 73` - Raw SQL execution with no logging - - `backend/apps/pisadmin/miscprocurement/cost_template_builder.py:317-345` - Extensive debug output with Unicode symbols - - `backend/dvadmin/utils/core_initialize.py:36, 56, 60, 86` - Initialization status prints - - `backend/dvadmin/system/tests.py:21, 44, 51, 53` - Test debugging output - -- **Impact:** Print statements can interfere with JSON API responses if any are output during request handling. They also expose internal logic in logs. -- **Recommendation:** Replace print statements with proper logging calls using the configured logger. - -### TODO Comments Found in Code - -**Severity:** Low - -- **Files:** - - `backend/dvadmin/utils/filters.py:127` - "TODO Rename this here and in `filter_queryset`" - - `backend/dvadmin/utils/filters.py:322, 329` - "TODO: remove assertion in 2.1" - -- **Impact:** Incomplete refactoring work can lead to confusion or breaking changes if assumptions change. -- **Recommendation:** Address TODOs before major version releases or refactoring phases. - -### Bare Except Clauses - -**Severity:** Medium - -- **File:** `backend/application/websocketConfig.py:90` -- **Issue:** - ```python - except Exception: - pass - ``` -- **Impact:** Silently ignores all exceptions, making debugging impossible and masking failures. -- **Recommendation:** Log exceptions or handle them with specific exception types. - -## Performance & Scalability Concerns - -### In-Memory Channel Layer for WebSocket - -**Severity:** High - -- **File:** `backend/application/settings.py:187-190` -- **Issue:** - ```python - CHANNEL_LAYERS = { - "default": { - "BACKEND": "channels.layers.InMemoryChannelLayer" - } - } - ``` -- **Impact:** WebSocket connections work only within a single worker process. With multiple uvicorn workers (configured as 8 in CLAUDE.md), WebSocket functionality will fail unpredictably across workers. -- **Recommendation:** Use Redis channel layer for production: - ```python - CHANNEL_LAYERS = { - 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', - 'CONFIG': { - "hosts": [('127.0.0.1', 6379)], - }, - }, - } - ``` - Note: The commented-out configuration exists at lines 192-199 but is not active. - -### Celery Running as Root - -**Severity:** Medium - -- **File:** `backend/application/celery.py:22` -- **Issue:** `platforms.C_FORCE_ROOT = True` -- **Impact:** Security risk running Celery worker processes as root user. -- **Recommendation:** Configure proper user/group for Celery workers in deployment. - -## Dependency Risks - -### Potentially Outdated Dependencies - -**Severity:** Medium - -- **File:** `backend/requirements.txt` -- **Observations:** - - Django 4.2.14 (current latest is 4.2.x) - Acceptable but check for security patches - - SimpleJWT 5.4.0 (5.5.0 available) - Minor version behind - - DRF 3.15.2 (3.15.4 available) - Minor version behind - - Channels 4.1.0 (4.2.x available) - - PyInstaller 6.9.0 - Major build tool dependency - -- **Recommendation:** Run `pip list --outdated` to identify packages needing updates, particularly security patches. - -## Operational Concerns - -### Hardcoded Infrastructure IPs - -**Severity:** High - -- **File:** `backend/conf/env.py` -- **Issue:** - ```python - DATABASE_HOST = '192.168.80.29' - REDIS_HOST = '177.10.0.15' - REDIS_PASSWORD = 'redis_5pGXn2' - ``` - These are actual production IPs and credentials in the codebase. -- **Impact:** If committed to version control, infrastructure credentials are exposed. Also prevents portability across environments. -- **Recommendation:** Use environment variables for all infrastructure configuration. Never commit real credentials. - -### No Docker Compose for Infrastructure - -**Severity:** Medium - -- **Observation:** No `docker-compose.yml` found in project root (only `backend/Dockerfile` and `web/Dockerfile`) -- **Impact:** No documented way to spin up the full stack (Django, Redis, database) locally. Makes local development and testing harder. -- **Recommendation:** Create `docker-compose.yml` that orchestrates backend, Redis, and database containers. - -### Log Directory Runtime Creation - -**Severity:** Low - -- **File:** `backend/application/settings.py:209-210` -- **Issue:** - ```python - if not os.path.exists(os.path.join(BASE_DIR, "logs")): - os.makedirs(os.path.join(BASE_DIR, "logs")) - ``` -- **Impact:** If the logs directory cannot be created (permissions issue), the application will crash on startup. -- **Recommendation:** Ensure logs directory exists before deployment via CI/CD or container entrypoint. - -## Known Issues Summary - -| Area | Severity | Issue | Files | -|------|----------|-------|-------| -| Secrets | Critical | Hardcoded SECRET_KEY | `application/settings.py:31` | -| Auth | Critical | MD5 password hashing | `backends.py`, `models.py`, `login.py` | -| Config | High | DEBUG=True in prod env | `conf/env.py:39` | -| Config | High | ALLOWED_HOSTS=* | `conf/env.py:48` | -| CORS | High | Allow all origins | `settings.py:179-181` | -| WebSocket | High | In-memory channel layer | `settings.py:189` | -| Secrets | High | Hardcoded IPs/passwords | `conf/env.py` | -| Code Quality | Medium | Bare except clauses | `websocketConfig.py:90` | -| Code Quality | Low | Debug print statements | Multiple files | -| Dependencies | Medium | Outdated packages | `requirements.txt` | - ---- - -*Concerns audit: 2026-04-01* diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md deleted file mode 100644 index 667c2e6..0000000 --- a/.planning/codebase/CONVENTIONS.md +++ /dev/null @@ -1,316 +0,0 @@ -# Coding Conventions - -**Analysis Date:** 2026-04-01 - -## Naming Patterns - -### Backend (Python/Django) - -**Files:** -- Python source files: snake_case (e.g., `email_utils.py`, `system_no_rule.py`) -- URL routing: `urls.py` -- Serializers: `serializers.py` -- Models: `models.py` -- Views: either single `views.py` or organized in `views/` directory with one file per resource - -**Classes:** -- Models: PascalCase (e.g., `Company`, `Supplier`, `SystemNoRule`) -- ViewSets: PascalCase ending with `ViewSet` (e.g., `CompanyViewSet`, `InquiryViewSet`) -- Serializers: PascalCase ending with `Serializer` (e.g., `CompanySerializer`, `InquirySerializer`) -- Custom serializers for create/update: `ModelNameCreateUpdateSerializer` - -**Functions/Methods:** -- snake_case (e.g., `allocate_system_number`, `get_request_username`) -- Private methods: prefixed with underscore (e.g., `_allocate_next_code_for_locked_rule`) - -**Database Fields:** -- snake_case (e.g., `company_code`, `supplier_name`, `create_datetime`) -- Primary key: `id` -- Foreign keys: `model_name_id` or just `field_name` (e.g., `quotation_no`) -- Timestamps: `create_datetime`, `update_datetime` -- User tracking: `createuser`, `updateuser` (char fields, not FK) - -**URL Patterns:** -- Router.register uses snake_case plural (e.g., `r'units'`, `r'companies'`, `r'inquiry'`) -- Full endpoint example: `/api/pisadmin/basicinfo/companies/` - -### Frontend (Vue 3/TypeScript) - -**Files:** -- Vue components: kebab-case in directory (e.g., `company/index.vue`) -- API modules: `api.ts` in same directory (e.g., `company/api.ts`) -- CRUD configuration: `.tsx` extension (e.g., `company/crud.tsx`) -- Stores: `userInfo.ts`, `dictionary.ts` -- Utilities: `service.ts`, `storage.ts`, `message.ts` - -**Components:** -- Vue components use ` - - -``` - -**FastCRUD Configuration Pattern:** -```typescript -export const createCrudOptions = function ({ crudExpose }: Partial): CreateCrudOptionsRet { - return { - crudOptions: { - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => api.AddObj(form), - editRequest: async ({ form, row }) => api.UpdateObj({ ...form, id: row.id }), - delRequest: async ({ row }) => api.DelObj(row.id) - }, - columns: { - company_code: { - title: '公司代码', - type: 'input', - search: { show: true }, - form: { rules: [{ required: true, message: '请输入公司代码' }] } - } - } - } - } -} -``` - -**API Module Pattern:** -```typescript -// /@/utils/service exports `request` -import { request } from '/@/utils/service' - -const apiPrefix = '/api/pisadmin/basicinfo/companies/' - -export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) -export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) -``` - -**Pinia Store Pattern:** -```typescript -export const useUserInfo = defineStore('userInfo', { - state: (): UserInfosStates => ({ - userInfos: { /* initial state */ } - }), - actions: { - async setUserInfos() { /* action */ } - } -}) -``` - -## Linting and Formatting Configuration - -### Frontend (ESLint) - -**Config File:** `web/.eslintrc.js` - -**Parser:** `vue-eslint-parser` with `@typescript-eslint/parser` - -**Key Rules:** -- `@typescript-eslint/no-unused-vars`: `'off'` (disabled) -- `@typescript-eslint/no-explicit-any`: `'off'` (disabled) -- `no-console`: `'error'` (console.log is error, use ElMessage instead) -- Vue component rules mostly `'off'` for flexibility - -**Run Lint Fix:** -```bash -yarn lint-fix -``` - -### Frontend (Prettier) - -**Config File:** `web/.prettierrc.js` - -**Key Settings:** -```javascript -{ - printWidth: 150, - tabWidth: 2, - useTabs: false, - semi: true, - singleQuote: true, - trailingComma: 'es5', - bracketSpacing: true, - arrowParens: 'always', - endOfLine: 'lf' -} -``` - -### Backend (Python) - -No explicit Python linting configuration detected. Python code follows PEP 8 implicitly. - -## Error Handling - -### Backend - -- Use `try/except` blocks with specific exception handling -- Use `serializers.ValidationError` for API validation errors -- Use `ErrorResponse` from dvadmin for consistent error responses -- Log errors with `logger = logging.getLogger(__name__)` - -### Frontend - -- Use `ElMessage.error()` for user-facing error messages -- Use try/catch with async/await -- API errors handled in `service.ts` interceptors - -## Logging - -### Backend -```python -import logging -logger = logging.getLogger(__name__) - -logger.warning("message with %s", value) -logger.exception("error message") # includes traceback -``` - -### Frontend -- `console.log` allowed for debugging (stripped in production build) -- Production uses `ElMessage` for user feedback -- No structured logging library detected - -## Comments and Documentation - -### Backend -- Chinese docstrings for business logic (e.g., `"""公司信息-序列化器"""`) -- Complex methods have inline comments explaining logic -- Use `# pragma: no cover` for untested branches - -### Frontend -- Component `name` attribute for debugging -- Chinese comments for complex business logic -- JSDoc style not strictly enforced - ---- - -*Convention analysis: 2026-04-01* diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md deleted file mode 100644 index 76532b2..0000000 --- a/.planning/codebase/INTEGRATIONS.md +++ /dev/null @@ -1,295 +0,0 @@ -# External Integrations - -**Analysis Date:** 2026-04-01 - -## Database Connection - -**Primary Database:** -- **Engine:** Microsoft SQL Server (via mssql-django 1.7) -- **Connection Driver:** ODBC Driver 18 for SQL Server -- **Database Name:** `pisdb` -- **Host:** `192.168.80.29` -- **Port:** `1433` -- **Credentials:** Configured in `backend/conf/env.py` -- **Configuration File:** `backend/conf/env.py` lines 14-24 - -**Connection Settings:** -```python -DATABASE_ENGINE = "mssql" -DATABASE_NAME = 'pisdb' -DATABASE_HOST = '192.168.80.29' -DATABASE_PORT = 1433 -DATABASE_USER = "test" -DATABASE_PASSWORD = '123456' -OPTIONS = { - "driver": "ODBC Driver 18 for SQL Server", - "extra_params": "Encrypt=yes;TrustServerCertificate=yes" -} -``` - -**Alternative Database Support:** -- MySQL 8.0 (via mysqlclient 2.2.0) -- PostgreSQL (via psycopg2 2.9.9) - -**Table Prefix:** `pis_` (Procurement Inquiry System) - -**Django Settings:** `backend/application/settings.py` lines 105-119 - -## Redis Configuration - -**Connection:** -- **Host:** `177.10.0.15` -- **Port:** `6379` -- **Password:** `redis_5pGXn2` -- **URL Format:** `redis://:redis_5pGXn2@177.10.0.15:6379` - -**Usage in Application:** -- Redis DB 1: General caching (`REDIS_DB`) -- Redis DB 3: Celery broker (`CELERY_BROKER_DB`) - -**Configuration File:** `backend/conf/env.py` lines 31-35 - -**Cache Backend Configuration:** -```python -REDIS_DB = 1 -CELERY_BROKER_DB = 3 -REDIS_PASSWORD = 'redis_5pGXn2' -REDIS_HOST = '177.10.0.15' -REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' -``` - -## Celery Task Queue - -**Broker:** Redis (same server as cache) - -**Configuration Source:** `backend/application/celery.py` - -**Celery Setup:** -- Auto-discovers tasks from all installed apps -- Uses Django settings namespace for configuration (`CELERY_*`) -- Supports tenant-aware mode if `django_tenants` is installed -- Runs with `C_FORCE_ROOT = True` for root execution - -**Retry Strategy:** -- Default retry delay: 180 seconds -- Maximum retries: 3 -- Decorator: `@retry_base_task_error()` - -**Task Result Storage:** Django Celery Results (`django_celery_results`) - -**Celery Beat Configuration:** -- Timezone: `Asia/Shanghai` -- Result backend stores task results in database - -**Django Settings Import:** `from django.conf:settings, namespace='CELERY'` - -**Installed Apps Integration:** -```python -app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) -``` - -## WebSocket / Django Channels - -**ASGI Application:** `backend/application/asgi.py` - -**Protocol Support:** -- HTTP (via Django) -- WebSocket (via Channels) - -**WebSocket URL Pattern:** -- Route: `ws//` -- Consumer: `MegCenter` (from `application.websocketConfig`) -- Routing: `backend/application/ws_routing.py` - -**Channel Layers:** -- Default (development): `InMemoryChannelLayer` -- Production: `channels_redis.core.RedisChannelLayer` (commented out) - -**Authentication:** -- WebSocket connections validated via JWT token in `service_uid` parameter -- Token decoded using Django `SECRET_KEY` -- User ID extracted from decoded token -- Channel group format: `user_{user_id}` - -**WebSocket Consumer Classes:** -- `DvadminWebSocket` - Base WebSocket consumer with JWT auth -- `MegCenter` - Message center consumer extending base class - -**Message Flow:** -1. Client connects to `ws://host/ws//` -2. Token validated, user_id extracted -3. User added to channel group `user_{user_id}` -4. Messages pushed to user's channel group -5. `websocket_push()` function sends messages to specific users - -**File:** `backend/application/websocketConfig.py` lines 57-114 - -## Authentication & Authorization - -**JWT Authentication:** -- **Package:** djangorestframework-simplejwt 5.4.0 -- **Header Type:** `JWT` -- **Access Token Lifetime:** 1440 minutes (24 hours) -- **Refresh Token Lifetime:** 1 day -- **Rotate Refresh Tokens:** Enabled - -**JWT Configuration (settings.py lines 321-329):** -```python -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440), - "REFRESH_TOKEN_LIFETIME": timedelta(days=1), - "AUTH_HEADER_TYPES": ("JWT",), - "ROTATE_REFRESH_TOKENS": True, -} -``` - -**Authentication Backends:** -- `rest_framework_simplejwt.authentication.JWTAuthentication` -- `rest_framework.authentication.SessionAuthentication` - -**Custom Backend:** `dvadmin.utils.backends.CustomBackend` - -**REST Framework Default Authentication (settings.py lines 304-307):** -```python -"DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework_simplejwt.authentication.JWTAuthentication", - "rest_framework.authentication.SessionAuthentication", -), -``` - -**Permission:** `rest_framework.permissions.IsAuthenticated` - -## Email Configuration - -**SMTP Configuration (backend/conf/env.py lines 53-68):** -```python -EMAIL_FROM = "eip@avc.co" -EMAIL_HOST_USER = "" -EMAIL_HOST_PASSWORD = "" -EMAIL_HOST = "mailflow.avc.co" -EMAIL_PORT = 25 -EMAIL_USE_SSL = False -EMAIL_USE_TLS = False -``` - -**Email Features:** -- Plain text connection (no SSL/TLS) -- System emails sent from `eip@avc.co` - -## Cloud Storage Integrations - -**Alibaba Cloud OSS:** -- Package: `oss2 2.19.1` -- Used for file storage - -**Tencent Cloud COS:** -- Package: `cos-python-sdk-v5 1.9.37` -- Used for file storage - -## API Documentation - -**Swagger/OpenAPI:** -- Package: `drf-yasg 1.21.7` -- Login URL: `/apiLogin/` -- Logout URL: `/rest_framework:logout/` -- Auto schema generation with custom schema class -- JSON editor enabled in Swagger UI - -**Configuration (settings.py lines 334-354):** -```python -SWAGGER_SETTINGS = { - "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}}, - "LOGIN_URL": "apiLogin/", - "LOGOUT_URL": "rest_framework:logout", - "JSON_EDITOR": True, - "DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema", -} -``` - -## CORS Configuration - -**CORS Settings (settings.py lines 177-181):** -```python -CORS_ORIGIN_ALLOW_ALL = True -CORS_ALLOW_CREDENTIALS = True -``` - -**CORS Middleware:** `corsheaders.middleware.CorsMiddleware` - -## Static Files & Media - -**Static Files:** -- URL: `/static/` -- Storage: Whitenoise with compression -- Storage Class: `whitenoise.storage.CompressedStaticFilesStorage` - -**Media Files:** -- URL: `/media/` -- Root: `media` directory in project - -**Configuration (settings.py lines 156-169):** -```python -STATIC_URL = "/static/" -STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] -MEDIA_ROOT = "media" -MEDIA_URL = "/media/" -STATICFILES_FINDERS = ( - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder" -) -``` - -## Proxy Configuration (Development) - -**Vite Dev Server Proxy (vite.config.ts lines 37-44):** -```typescript -proxy: { - '/gitee': { - target: 'https://gitee.com', - ws: true, - changeOrigin: true, - rewrite: (path) => path.replace(/^\/gitee/, ''), - }, -} -``` - -## Environment Variables - -**Configuration Source:** `backend/conf/env.py` - -**Key Environment Variables:** -| Variable | Purpose | -|----------|---------| -| `DATABASE_ENGINE` | Database backend type | -| `DATABASE_HOST` | Database server host | -| `DATABASE_PORT` | Database server port | -| `DATABASE_USER` | Database username | -| `DATABASE_PASSWORD` | Database password | -| `REDIS_HOST` | Redis server host | -| `REDIS_PASSWORD` | Redis password | -| `DEBUG` | Debug mode flag | -| `EMAIL_HOST` | SMTP server | -| `EMAIL_PORT` | SMTP port | - -**Note:** Actual credentials are stored in `backend/conf/env.py` (not committed to version control per best practices). - -## Key Dependencies Between Systems - -``` -Frontend (Vue 3) - | - | HTTP/REST + WebSocket - v -Backend (Django + DRF) - | - +---> Database (SQL Server/MySQL/PostgreSQL) - +---> Cache (Redis) - +---> Task Queue (Celery + Redis) - +---> WebSocket (Channels + Redis) - +---> Cloud Storage (OSS/COS) - +---> Email (SMTP) -``` - ---- - -*Integration audit: 2026-04-01* diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md deleted file mode 100644 index 60a2721..0000000 --- a/.planning/codebase/STACK.md +++ /dev/null @@ -1,227 +0,0 @@ -# Technology Stack - -**Analysis Date:** 2026-04-01 - -## Languages - -**Primary:** -- Python 3.x - Backend development -- TypeScript 4.9.4 - Frontend development -- JavaScript (ESNext) - Frontend runtime - -**Secondary:** -- Vue 3.4.38 - Frontend UI framework -- HTML/CSS - Frontend markup and styling - -## Backend Framework - -**Django Core:** -- Django 4.2.14 - Web framework -- Django REST Framework 3.15.2 - REST API development -- channels 4.1.0 - WebSocket support -- Celery 5.x (via dvadmin3-celery 3.1.6) - Async task queue - -**Authentication:** -- djangorestframework-simplejwt 5.4.0 - JWT token authentication -- django-simple-captcha 0.6.0 - CAPTCHA verification - -**Database:** -- mssql-django 1.7 - SQL Server driver (configured for pisdb) -- mysqlclient 2.2.0 - MySQL driver (also available) -- psycopg2 2.9.9 - PostgreSQL driver - -**Key Django Plugins:** -- django-cors-headers 4.4.0 - Cross-origin resource sharing -- django-filter 24.2 - Database query filtering -- django-ranged-response 0.2.0 - HTTP range response support -- django-restql 0.15.4 - GraphQL-like query language for REST -- drf-yasg 1.21.7 - Swagger/OpenAPI documentation -- django-timezone-field 7.0 - Timezone support -- django-comment-migrate 0.1.7 - Comment migration utility -- Pillow 10.4.0 - Image processing -- openpyxl 3.1.5 - Excel file handling -- requests 2.32.4 - HTTP client -- pypinyin 0.51.0 - Chinese pinyin conversion -- ua-parser 0.18.0 - User agent parsing -- user-agents 2.2.0 - User agent detection -- six 1.16.0 - Python 2/3 compatibility -- whitenoise 6.7.0 - Static file serving -- pyparsing 3.1.2 - Parsing expression grammar -- typing-extensions 4.12.2 - Type hint extensions -- tzlocal 5.2 - Timezone localization - -**Server:** -- uvicorn 0.30.3 - ASGI server -- gunicorn 23.0.0 - WSGI server -- gevent 24.2.1 - Async networking library - -**Cloud Storage:** -- oss2 2.19.1 - Alibaba Cloud OSS integration -- cos-python-sdk-v5 1.9.37 - Tencent Cloud COS integration - -## Frontend Framework - -**Core:** -- Vue 3.4.38 - Progressive JavaScript framework -- TypeScript 4.9.4 - Typed superset of JavaScript -- Vite 5.4.1 - Next-generation frontend build tool -- Pinia 2.0.28 - State management -- Vue Router 4.4.3 - SPA routing -- Vue I18n 9.14.0 - Internationalization - -**UI Component Libraries:** -- Element Plus 2.8.0 - UI component library -- @element-plus/icons-vue 2.3.1 - Element Plus icons -- Vant 4.9.19 - Mobile UI component library -- vant4-kit 1.0.3 - Vant utility kit - -**FastCRUD:** -- @fast-crud/fast-crud 1.21.2 - Quick CRUD development -- @fast-crud/fast-extends 1.21.2 - FastCRUD extensions -- @fast-crud/ui-element 1.21.2 - Element Plus UI for FastCRUD -- @fast-crud/ui-interface 1.21.2 - FastCRUD UI interface - -**Data Visualization:** -- echarts 5.5.1 - Interactive charting library -- echarts-gl 2.0.9 - ECharts 3D visualization -- echarts-wordcloud 2.1.0 - Word cloud for ECharts -- vxe-table 4.6.18 - Grid/table component -- xe-utils 3.5.30 - Utility functions for vxe-table - -**Build & Development:** -- @vitejs/plugin-vue 5.1.2 - Vue plugin for Vite -- @vitejs/plugin-vue-jsx 4.0.1 - Vue JSX support -- vite-plugin-vue-setup-extend 0.4.0 - Vue setup syntax extension -- sass 1.56.2 - CSS preprocessor -- less 4.3.0 - CSS preprocessor -- autoprefixer 10.4.20 - CSS vendor prefixing -- postcss 8.4.21 - CSS transformation tool -- tailwindcss 3.2.7 - Utility-first CSS framework -- rollup 4.60.1 - Module bundler - -**Utilities:** -- axios 1.7.4 - HTTP client -- qs 6.11.0 - Query string parsing -- lodash-es 4.17.21 - Utility library -- js-cookie 3.0.5 - Cookie handling -- mitt 3.0.1 - Tiny event emitter -- nprogress 0.2.0 - Progress bar -- sortablejs 1.15.0 - Drag and drop sorting -- vue-draggable-plus 0.6.0 - Vue draggable component -- screenfull 6.0.2 - Fullscreen API wrapper -- print-js 1.6.0 - Print functionality -- qrcodejs2-fixes 0.0.2 - QR code generation -- cropperjs 1.6.2 - Image cropping -- vue-cropper 1.0.8 - Vue image cropper -- countup.js 2.8.0 - Animated number counting -- e-icon-picker 2.1.1 - Icon picker -- element-tree-line 0.2.1 - Tree line for Element -- font-awesome 4.7.0 - Icon library -- @iconify/vue 4.1.2 - Icon library -- vue-clipboard3 2.0.0 - Clipboard API wrapper -- ts-md5 1.3.1 - MD5 hashing -- vue-grid-layout 3.0.0-beta1 - Grid layout -- vue-qr 4.0.9 - QR code for Vue -- jsplumb 2.15.6 - Visual connectivity -- @wangeditor/editor 5.1.23 - Rich text editor -- @wangeditor/editor-for-vue 5.1.12 - Vue wrapper for WangEditor -- js-table2excel 1.1.2 - Table to Excel export -- date-holidays 3.24.1 - Holiday data -- lunar-javascript 1.7.1 - Chinese lunar calendar -- upgrade 1.1.0 - Upgrade utility -- @great-dream/dvadmin3-celery-web 3.1.3 - Celery web UI - -**Development Tools:** -- eslint 8.57.1 - Linting -- eslint-plugin-vue 9.27.0 - Vue ESLint plugin -- @typescript-eslint/parser 5.46.0 - TypeScript ESLint parser -- @typescript-eslint/eslint-plugin 5.46.0 - TypeScript ESLint plugin -- prettier 2.8.1 - Code formatting -- vue-eslint-parser 9.4.3 - Vue template parser -- @types/node 18.19.42 - Node.js type definitions -- @types/nprogress 0.2.3 - NProgress type definitions -- @types/sortablejs 1.15.8 - SortableJS type definitions -- @types/lodash 4.17.7 - Lodash type definitions -- baseline-browser-mapping 2.9.19 - Browser capability mapping - -## Infrastructure - -**Database:** -- MySQL 8.0 - Primary relational database (configured) -- SQL Server (via ODBC Driver 18) - Alternative database option - -**Cache & Queue:** -- Redis - Caching, session storage, Celery broker -- Celery - Async task processing - -**Container:** -- Docker - Containerization -- Docker Compose - Multi-container orchestration - -## Configuration Files - -**Backend:** -- `backend/conf/env.py` - Environment configuration (DB, Redis, Email) -- `backend/application/settings.py` - Django settings (imports from conf/env.py) -- `backend/application/celery.py` - Celery configuration -- `backend/application/asgi.py` - ASGI configuration (WebSocket support) -- `backend/application/ws_routing.py` - WebSocket URL routing -- `backend/application/websocketConfig.py` - WebSocket consumer implementation - -**Frontend:** -- `web/package.json` - Node.js dependencies and scripts -- `web/vite.config.ts` - Vite build configuration -- `web/tsconfig.json` - TypeScript compiler options - -## Project Structure - -``` -pis/ -├── backend/ -│ ├── application/ # Django project configuration -│ │ ├── settings.py # Main Django settings -│ │ ├── urls.py # Root URL configuration -│ │ ├── asgi.py # ASGI application (HTTP + WebSocket) -│ │ ├── celery.py # Celery task configuration -│ │ ├── ws_routing.py # WebSocket routing -│ │ └── websocketConfig.py # WebSocket consumers -│ ├── apps/ -│ │ ├── pisadmin/ # Custom procurement app -│ │ │ ├── basicinfo/ # Company, currency, supplier, unit -│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections -│ │ └── pissupplier/ # Supplier quotation module -│ ├── dvadmin/ # Core RBAC system (dvadmin3) -│ │ └── system/ # Users, roles, menus, departments -│ └── conf/ -│ └── env.py # Environment configuration -├── web/ # Vue 3 frontend -│ ├── src/ -│ │ ├── views/ # Page components -│ │ ├── api/ # API clients -│ │ ├── stores/ # Pinia stores -│ │ └── router/ # Vue Router config -│ ├── vite.config.ts # Vite configuration -│ └── tsconfig.json # TypeScript config -└── docker-compose.yml # Docker deployment -``` - -## Build Commands - -**Backend:** -```bash -python manage.py makemigrations # Create migrations -python manage.py migrate # Apply migrations -python manage.py runserver # Development server -uvicorn application.asgi:application --workers 8 # Production -``` - -**Frontend:** -```bash -yarn dev # Development server (port 8080) -yarn build # Production build -yarn lint-fix # Lint and auto-fix -``` - ---- - -*Stack analysis: 2026-04-01* diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md deleted file mode 100644 index 8072c21..0000000 --- a/.planning/codebase/STRUCTURE.md +++ /dev/null @@ -1,247 +0,0 @@ -# Codebase Structure - -**Analysis Date:** 2026-04-01 - -## Directory Layout - -``` -pis/ -├── backend/ -│ ├── application/ # Django project config (settings, urls, asgi) -│ ├── apps/ -│ │ ├── pisadmin/ # Custom procurement app -│ │ │ ├── basicinfo/ # Company, currency, supplier, unit, system no -│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections -│ │ └── pissupplier/ # Supplier quotation module -│ ├── conf/ -│ │ └── env.py # Environment config (DB, Redis, Email) -│ ├── dvadmin/ # Base RBAC system -│ │ ├── system/ # Users, roles, menus, departments -│ │ └── utils/ # CoreModel, CustomViewSet, filters, etc. -│ ├── plugins/ # Plugin modules (celery, etc.) -│ ├── static/ # Static files -│ ├── templates/ # Django templates -│ └── util/ # Utility scripts -├── web/ # Vue 3 frontend -│ └── src/ -│ ├── api/ # API client modules -│ ├── components/ # Reusable Vue components -│ ├── layout/ # App layout (header, sidebar, nav) -│ ├── router/ # Vue Router config -│ ├── stores/ # Pinia state stores -│ ├── utils/ # Helper functions -│ └── views/ # Page components -├── docker-compose.yml # Container deployment (not in repo root) -├── Makefile # Build scripts -└── scripts/ # Utility scripts -``` - -## Backend Directory Purposes - -**`backend/application/`:** -- Purpose: Django project configuration entry point -- Key files: - - `settings.py`: Main Django settings (imports from `conf/env.py`) - - `urls.py`: Root URL routing (includes all app URLs) - - `asgi.py`: ASGI config for HTTP + WebSocket - - `ws_routing.py`: WebSocket URL patterns - - `celery.py`: Celery task configuration - -**`backend/conf/`:** -- Purpose: Environment-specific configuration -- Key file: `env.py` - Contains DATABASE_*, REDIS_*, EMAIL_* settings -- This file contains credentials - DO NOT commit to version control - -**`backend/apps/pisadmin/basicinfo/`:** -- Purpose: Master data for procurement (companies, currencies, suppliers, units) -- Structure: - - `models.py`: Company, Currency, Supplier, SupplierUser, Unit, SystemNoRule, EmailNotice - - `views/`: company.py, currency.py, supplier.py, unit.py, system_no_rule.py, email_template.py, email_utils.py - - `urls.py`: Routes to views - - `admin.py`: Django admin registration - -**`backend/apps/pisadmin/miscprocurement/`:** -- Purpose: RFQ (Request for Quotation) and cost template management -- Structure: - - `models.py`: Inquiry, InquirySupplier, InquiryAttachment, InquiryMaterialCost, InquiryProcessCost, InquiryOtherCost, InquiryProfitCost, InquiryRfqItem, CostEstimateTemplateHead, CostEstimateTemplateBody, MiscLowPriceHeader, MiscLowPriceDetail, MiscNegotiationRecords, RFQOperationLogs - - `views.py`: ViewSets for all models (1500+ lines with complex workflow logic) - - `serializers.py`: DRF serializers - - `urls.py`: Routes to views - - `cost_template_builder.py`: Template construction utility - -**`backend/apps/pissupplier/`:** -- Purpose: Supplier quotation management (separate from pisadmin for supplier-facing access) -- Structure: - - `models.py`: QuotationMaster, QuotationMaterial, QuotationProcess, QuotationOther, QuotationProfit, QuotationItem, QuotationAttachment - - `views.py`: Quotation ViewSets - - `serializers.py`: DRF serializers - - `urls.py`: Routes to views - -**`backend/dvadmin/system/`:** -- Purpose: Base RBAC system (users, roles, menus, departments, logs) -- Structure: - - `models.py`: Users, Role, Menu, MenuButton, MenuField, FieldPermission, Dept, Post, OperationLog, LoginLog, SystemConfig, Dictionary, FileList, Area, ApiWhiteList, MessageCenter - - `views/`: ViewSets for each model - - `urls.py`: Routes to views - - `fixtures/`: Initial data fixtures - - `management/commands/`: Django commands (init, init_area, check_dept_users) - -**`backend/dvadmin/utils/`:** -- Purpose: Reusable utilities for all apps -- Key files: - - `viewset.py`: CustomModelViewSet base class with standardized responses - - `models.py`: CoreModel, SoftDeleteModel base classes - - `filters.py`: DataLevelPermissionMargeFilter, CoreModelFilterBankend - - `permission.py`: CustomPermission class - - `pagination.py`: CustomPagination class - - `serializers.py`: Common serializer utilities - - `exception.py`: Custom exception handler - - `json_response.py`: SuccessResponse, DetailResponse, ErrorResponse wrappers - -## Frontend Directory Purposes - -**`web/src/api/`:** -- Purpose: API client modules -- Structure: - - `login/index.ts`: Login/auth API - - `menu/index.ts`: Menu/permission API - - `miscprocurement/priceTemplate.ts`: Cost template API - - `procurement/priceTemplate.ts`: (duplicate or alternative) - -**`web/src/components/`:** -- Purpose: Reusable Vue components -- Key components: - - `table/index.vue`: Custom table component with FastCRUD - - `auth/auth*.vue`: Permission directive components - - `fileSelector/`: File upload component - - `foreignKey/`: Foreign key selector - - `importExcel/`: Excel import component - -**`web/src/layout/`:** -- Purpose: Application layout structure -- Subdirectories: - - `component/`: aside.vue, header.vue, main.vue - - `navBars/`: breadcrumb, tagsView, user menu - - `navMenu/`: vertical and horizontal menu components - - `main/`: classic, columns, defaults, transverse layout variants - - `routerView/`: iframes, link, parent route views - -**`web/src/router/`:** -- Purpose: Vue Router configuration -- Key files: - - `index.ts`: Router creation, beforeEach guards, token validation - - `route.ts`: Static and dynamic route definitions - - `frontEnd.ts`: Frontend-controlled routing logic - - `backEnd.ts`: Backend-controlled routing logic - -**`web/src/stores/`:** -- Purpose: Pinia state management -- Key stores: - - `userInfo.ts`: User authentication state - - `frontendMenu.ts`: Menu tree cache - - `themeConfig.ts`: UI theme settings - - `tagsViewRoutes.ts`: Open tab tracking - - `routesList.ts`: Available routes - - `permission.ts`: Permission state - -**`web/src/views/`:** -- Purpose: Page-level Vue components -- Structure mirrors backend apps: - - `pisadmin/basicinfo/`: company, currency, supplier, unit, systemno, emailnotice - - `pisadmin/miscprocurement/`: cost_template, misc_materials, misc_parts, misc_stations - - `system/`: admin pages (dept, menu, role, user, dictionary, config, etc.) - - `pissupplier/` (if exists): Supplier-facing pages - -## Key Configuration File Locations - -**Backend Configuration:** -- `backend/application/settings.py` - Main Django settings -- `backend/conf/env.py` - Environment variables (DB, Redis, Email) -- `backend/application/urls.py` - URL routing -- `backend/application/asgi.py` - ASGI/WebSocket config - -**Frontend Configuration:** -- `web/src/settings.ts` - Frontend settings (API base URLs, app info) -- `web/src/main.ts` - Vue app initialization -- `web/src/App.vue` - Root Vue component -- `web/vite.config.ts` - Vite build configuration - -**Environment/Secrets (DO NOT COMMIT):** -- `backend/conf/env.py` - Database, Redis, Email credentials -- `.env` files (if present) - -## Custom Apps vs Base dvadmin Structure - -**Base dvadmin (`dvadmin/`):** -- Provided by django-vue3-admin template -- Contains generic functionality: user management, role management, menu management, RBAC -- Should NOT be modified for custom business logic -- Extended by custom apps for domain-specific features - -**Custom Apps (`apps/pisadmin/`, `apps/pissupplier/`):** -- `pisadmin`: Internal procurement admin (RFQ, cost templates, supplier master, etc.) -- `pissupplier`: Supplier portal (quotations) -- Custom apps import and extend dvadmin's `CustomModelViewSet`, `CoreModel`, `CustomPermission` -- Custom apps define their own models, serializers, views, urls - -**Extension Pattern:** -```python -from dvadmin.utils.viewset import CustomModelViewSet - -class InquiryViewSet(CustomModelViewSet): - queryset = Inquiry.objects.all() - serializer_class = InquirySerializer - # Inherits: create, list, retrieve, update, destroy, multiple_delete, get_by_ids - # Custom actions added via @action decorator -``` - -## Docker/Container Structure - -The project uses Docker for deployment (docker-compose not present in current branch): - -**Backend Container:** -- Based on Python 3.x -- Runs Django via uvicorn (ASGI) -- Port: 8000 -- Depends on: MySQL, Redis - -**Frontend Container:** -- Based on Node.js for build -- nginx for serving built SPA -- Port: 8080 (dev), 80 (prod) - -**External Dependencies:** -- MySQL 8.0: Database (configured via `conf/env.py`) -- Redis: Caching and Celery broker -- SMTP: Email notifications - -## Where to Add New Code - -**New Model (Backend):** -1. Add to appropriate app's `models.py` -2. Inherit from `CoreModel` -3. Create serializer in `serializers.py` -4. Create ViewSet extending `CustomModelViewSet` -5. Add URL route in app's `urls.py` -6. Run migrations: `python manage.py makemigrations apps..` -7. Apply: `python manage.py migrate` - -**New API Endpoint (Backend):** -1. If CRUD: Add ViewSet method or use default actions -2. If custom: Add `@action` method to ViewSet -3. Register route in app `urls.py` with `router.register()` - -**New Page (Frontend):** -1. Create Vue component in `web/src/views///` -2. Add API client in `web/src/api//` -3. Menu added via backend `Menu` model (not frontend route config) -4. Component uses FastCRUD pattern with custom table and form components - -**New Store (Frontend):** -1. Create in `web/src/stores/modules/` or root stores -2. Use Pinia `defineStore` -3. Persist sensitive data to Session storage - ---- - -*Structure analysis: 2026-04-01* diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md deleted file mode 100644 index 1300ff4..0000000 --- a/.planning/codebase/TESTING.md +++ /dev/null @@ -1,238 +0,0 @@ -# Testing Patterns - -**Analysis Date:** 2026-04-01 - -## Test Framework - -### Backend (Python/Django) - -**No dedicated test framework detected.** - -- No `tests.py` files in Django apps -- No `pytest` configuration -- No `unittest` test suites -- Migrations are generated via `python manage.py makemigrations` - -### Frontend (Vue 3/TypeScript) - -**No test framework detected.** - -- No Jest, Vitest, or other test runner configuration -- No `.test.ts`, `.spec.ts` files in `web/src/` -- Build and lint commands only, no test commands in `package.json` - -## Test File Organization - -### Backend Structure - -``` -backend/ -├── application/ # Django settings -├── apps/ -│ ├── pisadmin/ # Custom procurement app -│ │ ├── basicinfo/ -│ │ │ ├── models.py -│ │ │ ├── views/ -│ │ │ ├── urls.py -│ │ │ └── migrations/ -│ │ └── miscprocurement/ -│ │ ├── models.py -│ │ ├── serializers.py -│ │ ├── views.py -│ │ └── migrations/ -│ └── pissupplier/ -└── dvadmin/ # Core RBAC system -``` - -**No `tests.py` directories or test files in application code.** - -### Frontend Structure - -``` -web/ -├── src/ -│ ├── api/ -│ ├── assets/ -│ ├── components/ -│ ├── directive/ -│ ├── layout/ -│ ├── plugin/ -│ ├── router/ -│ ├── stores/ -│ ├── theme/ -│ ├── types/ -│ ├── utils/ -│ ├── views/ # Page components -│ │ ├── pisadmin/ -│ │ │ ├── basicinfo/ -│ │ │ │ ├── company/ -│ │ │ │ │ ├── index.vue -│ │ │ │ ├── api.ts -│ │ │ │ └── crud.tsx -│ │ │ └── miscprocurement/ -│ │ │ ├── rfqmiscellaneous/ -│ │ │ └── cost_template/ -│ │ ├── pissupplier/ -│ │ └── system/ -│ ├── App.vue -│ └── main.ts -└── package.json -``` - -**No test files (`*.test.ts`, `*.spec.ts`) in `web/src/` directory.** - -## Available Scripts - -### Frontend (`web/package.json`) - -```json -{ - "scripts": { - "dev": "vite --force", - "build:dev": "vite build --mode development", - "build": "vite build", - "build:local": "vite build --mode local_prod", - "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/", - "build:flowH5": "vite build --config flowH5.config.ts" - } -} -``` - -**No test-related scripts.** - -### Backend - -```bash -# Migrations -python manage.py makemigrations apps.pisadmin.apps.pisadmin -python manage.py makemigrations apps.pissupplier -python manage.py migrate - -# Development server -python manage.py runserver 0.0.0.0:8000 - -# Production-like -uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 -``` - -**No test commands.** - -## CI/CD Pipeline - -**No CI/CD configuration detected.** - -- No GitHub Actions workflows (`.github/workflows/`) -- No `.gitlab-ci.yml` -- No Jenkinsfile -- Docker Compose present but no automated testing in Dockerfile - -## Coverage - -**No coverage tools configured.** - -- No `coverage.py` configuration -- No `pytest-cov` or similar -- No Istanbul/nyc for frontend - -## Manual Testing Approach - -Based on project structure, manual testing likely involves: - -### Backend API Testing - -1. **Django Admin Interface** - Models registered in `admin.py` provide basic CRUD -2. **DRF Browsable API** - Available at `/api/` endpoints for interactive testing -3. **Direct HTTP requests** - Using curl or Postman - -### Frontend Testing - -1. **Browser testing** - Manual UI verification via `yarn dev` -2. **FastCRUD configuration** - CRUD options in `crud.tsx` files control behavior -3. **Vue DevTools** - For state inspection - -## What Could Be Tested - -### Backend - -- **ViewSet endpoints**: Each ViewSet handles CRUD + custom actions -- **Serializer validation**: Input/output transformation -- **Model methods**: Business logic like `SystemNoRule.allocate_system_number()` -- **Transaction integrity**: Database operations wrapped in `transaction.atomic()` -- **Status workflow**: Inquiry state machine (open -> confirmed -> published -> quoting -> quote_ended -> bargaining -> negotiated -> approved -> awarded) - -### Frontend - -- **FastCRUD options**: Configuration in `createCrudOptions()` -- **API integration**: Request/response handling -- **Store actions**: Pinia store mutations -- **Router guards**: Authentication and authorization checks - -## Recommendations for Testing Setup - -### Backend - -Add `pytest` with `pytest-django`: - -``` -# requirements.txt additions -pytest==7.4.0 -pytest-django==4.5.0 -pytest-cov==4.1.0 -``` - -Create test files: -``` -backend/apps/pisadmin/basicinfo/tests.py -backend/apps/pisadmin/miscprocurement/tests.py -``` - -### Frontend - -Add Vitest for testing: - -```bash -yarn add -D vitest @vue/test-utils jsdom -``` - -Add test script: -```json -{ - "scripts": { - "test": "vitest", - "coverage": "vitest run --coverage" - } -} -``` - -Create test files: -``` -web/src/views/pisadmin/basicinfo/company/__tests__/Company.spec.ts -``` - -## Build and Deployment - -### Build Commands - -**Frontend:** -```bash -cd web -yarn install -yarn build # Production build -yarn build:dev # Development build -``` - -**Backend:** -```bash -cd backend -pip install -r requirements.txt -python manage.py migrate -python manage.py init # Create superuser -``` - -### Docker - -The project uses Docker for deployment but no test containers. - ---- - -*Testing analysis: 2026-04-01* diff --git a/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md b/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md deleted file mode 100644 index ded7b06..0000000 --- a/docs/superpowers/plans/2026-04-01-dashboard-redesign-plan.md +++ /dev/null @@ -1,1085 +0,0 @@ -# Dashboard Redesign Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Implement role-based buyer/supplier dashboard with unified API, real business data, and Vue 3 frontend components. - -**Architecture:** Role-based data filtering via unified `/api/dashboard/` endpoint. Backend computes KPIs from Inquiry/QuotationMaster/Supplier tables. Frontend uses Pinia store with separate buyer/supplier state branches. - -**Tech Stack:** Django REST Framework, Vue 3 + TypeScript + Pinia + ECharts - ---- - -## Backend Implementation - -### File Structure - -**Create:** -- `backend/apps/pisadmin/dashboard/__init__.py` -- `backend/apps/pisadmin/dashboard/apps.py` -- `backend/apps/pisadmin/dashboard/urls.py` -- `backend/apps/pisadmin/dashboard/serializers.py` -- `backend/apps/pisadmin/dashboard/views.py` - -**Modify:** -- `backend/application/urls.py` - add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` - ---- - -### Task 1: Create Dashboard App Skeleton - -- [ ] **Step 1: Create `backend/apps/pisadmin/dashboard/__init__.py`** -```python -``` - -- [ ] **Step 2: Create `backend/apps/pisadmin/dashboard/apps.py`** -```python -from django.apps import AppConfig - - -class DashboardConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'apps.pisadmin.dashboard' -``` - -- [ ] **Step 3: Create `backend/apps/pisadmin/dashboard/urls.py`** -```python -from django.urls import path -from .views import DashboardView - -urlpatterns = [ - path('', DashboardView.as_view(), name='dashboard'), -] -``` - -- [ ] **Step 4: Register app in `backend/application/settings.py`** -Add `'apps.pisadmin.dashboard'` to `INSTALLED_APPS` list. - -- [ ] **Step 5: Add URL to `backend/application/urls.py`** -Add `path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls"))` to urlpatterns. - -- [ ] **Step 6: Commit** -```bash -git add backend/apps/pisadmin/dashboard/ backend/application/urls.py backend/application/settings.py -git commit -m "feat(dashboard): create dashboard app skeleton with URL routing" -``` - ---- - -### Task 2: Implement Dashboard Serializers - -- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/serializers.py`** - -```python -from rest_framework import serializers - - -class BuyerKPISerializer(serializers.Serializer): - total_inquiries = serializers.IntegerField() - pending_inquiries = serializers.IntegerField() - completed_quotes = serializers.IntegerField() - total_suppliers = serializers.IntegerField() - - -class BuyerTaskSerializer(serializers.Serializer): - id = serializers.IntegerField() - title = serializers.CharField() - inquiry_no = serializers.CharField() - status = serializers.CharField() - created_at = serializers.DateTimeField() - - -class SupplierKPISerializer(serializers.Serializer): - total_quotes = serializers.IntegerField() - pending_quotes = serializers.IntegerField() - won_quotes = serializers.IntegerField() - conversion_rate = serializers.FloatField() - - -class SupplierQuoteSerializer(serializers.Serializer): - id = serializers.IntegerField() - inquiry_no = serializers.CharField() - item_name = serializers.CharField() - quantity = serializers.IntegerField() - unit = serializers.CharField() - deadline = serializers.DateTimeField() - - -class MessageSerializer(serializers.Serializer): - id = serializers.IntegerField() - title = serializers.CharField() - content = serializers.CharField() - is_read = serializers.BooleanField() - created_at = serializers.DateTimeField() - - -class TrendSerializer(serializers.Serializer): - month = serializers.CharField() - count = serializers.IntegerField(required=False) - quotes = serializers.IntegerField(required=False) - won = serializers.IntegerField(required=False) - - -class BuyerDashboardSerializer(serializers.Serializer): - kpi = BuyerKPISerializer() - tasks = BuyerTaskSerializer(many=True) - messages = MessageSerializer(many=True) - trend = TrendSerializer(many=True) - - -class SupplierDashboardSerializer(serializers.Serializer): - kpi = SupplierKPISerializer() - pending_quotes = SupplierQuoteSerializer(many=True) - messages = MessageSerializer(many=True) - trend = TrendSerializer(many=True) - - -class DashboardResponseSerializer(serializers.Serializer): - buyer = BuyerDashboardSerializer(allow_null=True) - supplier = SupplierDashboardSerializer(allow_null=True) -``` - -- [ ] **Step 2: Commit** -```bash -git add backend/apps/pisadmin/dashboard/serializers.py -git commit -m "feat(dashboard): add dashboard serializers" -``` - ---- - -### Task 3: Implement Dashboard View - -- [ ] **Step 1: Write `backend/apps/pisadmin/dashboard/views.py`** - -```python -from datetime import timedelta - -from django.utils import timezone -from django.db.models import Count -from django.db.models.functions import TruncMonth -from rest_framework.views import APIView -from rest_framework.response import Response - -from apps.pisadmin.miscprocurement.models import Inquiry -from apps.pissupplier.models import QuotationMaster -from apps.pisadmin.basicinfo.models import Supplier - - -def get_buyer_kpi(user): - """采购方 KPI 计算""" - total_inquiries = Inquiry.objects.filter(create_user=user.username).count() - pending_inquiries = Inquiry.objects.filter(create_user=user.username, status__in=[1, 2, 3]).count() - completed_quotes = QuotationMaster.objects.filter( - inquiry_no__in=Inquiry.objects.filter(create_user=user.username).values('inquiry_no') - ).count() - total_suppliers = Supplier.objects.count() - return { - 'total_inquiries': total_inquiries, - 'pending_inquiries': pending_inquiries, - 'completed_quotes': completed_quotes, - 'total_suppliers': total_suppliers, - } - - -def get_buyer_tasks(user): - """采购方待办任务""" - six_months_ago = timezone.now() - timedelta(days=180) - inquiries = Inquiry.objects.filter( - create_user=user.username, - status__in=[1, 2, 3] - ).order_by('-create_time')[:10] - return [ - { - 'id': i.id, - 'title': i.title, - 'inquiry_no': i.inquiry_no, - 'status': dict(Inquiry.STATUS_CHOICES).get(i.status, str(i.status)), - 'created_at': i.create_time, - } - for i in inquiries - ] - - -def get_supplier_kpi(user): - """供应商 KPI 计算""" - total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() - pending_quotes = QuotationMaster.objects.filter(supplier_code=user.username, status__in=[1, 2]).count() - won_quotes = QuotationMaster.objects.filter(supplier_code=user.username, is_awarded=1).count() - conversion_rate = (won_quotes / total_quotes * 100) if total_quotes > 0 else 0.0 - return { - 'total_quotes': total_quotes, - 'pending_quotes': pending_quotes, - 'won_quotes': won_quotes, - 'conversion_rate': round(conversion_rate, 2), - } - - -def get_supplier_pending_quotes(user): - """供应商待报价清单""" - quotes = QuotationMaster.objects.filter( - supplier_code=user.username, - status__in=[1, 2] - ).order_by('-creattime')[:10] - result = [] - for q in quotes: - inquiry = Inquiry.objects.filter(inquiry_no=q.inquiry_no).first() - result.append({ - 'id': q.autoid, - 'inquiry_no': q.inquiry_no, - 'item_name': inquiry.title if inquiry else '', - 'quantity': 0, - 'unit': '', - 'deadline': q.quote_deadline, - }) - return result - - -def get_trend_data(user, is_buyer=True): - """近6个月趋势数据""" - six_months_ago = timezone.now() - timedelta(days=180) - if is_buyer: - data = ( - Inquiry.objects.filter( - create_user=user.username, - create_time__gte=six_months_ago - ) - .annotate(month=TruncMonth('create_time')) - .values('month') - .annotate(count=Count('id')) - .order_by('month') - ) - else: - data = ( - QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__gte=six_months_ago - ) - .annotate(month=TruncMonth('creattime')) - .values('month') - .annotate(quotes=Count('id')) - .order_by('month') - ) - # Add won counts - result = [] - for d in data: - won = QuotationMaster.objects.filter( - supplier_code=user.username, - creattime__month=d['month'].month, - creattime__year=d['month'].year, - is_awarded=1 - ).count() - result.append({ - 'month': d['month'].strftime('%Y-%m'), - 'quotes': d['quotes'], - 'won': won, - }) - return result - return [{'month': d['month'].strftime('%Y-%m'), 'count': d['count']} for d in data] - - -def get_messages(user): - """消息通知 - 占位实现,后续接入通知系统""" - return [] - - -class DashboardView(APIView): - """看板数据视图""" - - def get(self, request): - user = request.user - if not user or not user.is_authenticated: - return Response({'buyer': None, 'supplier': None}) - - # 判断用户角色 - # 这里需要根据实际角色系统判断,暂用用户名特征区分 - # 供应商用户:有 supplier_code 在 QuotationMaster 中 - has_supplier_role = QuotationMaster.objects.filter(supplier_code=user.username).exists() - # 采购方用户:有 create_user 在 Inquiry 中 - has_buyer_role = Inquiry.objects.filter(create_user=user.username).exists() - - response_data = {'buyer': None, 'supplier': None} - - if has_buyer_role: - response_data['buyer'] = { - 'kpi': get_buyer_kpi(user), - 'tasks': get_buyer_tasks(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=True), - } - - if has_supplier_role: - response_data['supplier'] = { - 'kpi': get_supplier_kpi(user), - 'pending_quotes': get_supplier_pending_quotes(user), - 'messages': get_messages(user), - 'trend': get_trend_data(user, is_buyer=False), - } - - return Response(response_data) -``` - -- [ ] **Step 2: Commit** -```bash -git add backend/apps/pisadmin/dashboard/views.py -git commit -m "feat(dashboard): implement dashboard view with role-based data filtering" -``` - ---- - -## Frontend Implementation - -### File Structure - -**Create:** -- `web/src/api/pisadmin/dashboard.ts` -- `web/src/stores/modules/dashboard.ts` -- `web/src/views/pisadmin/dashboard/index.vue` -- `web/src/views/pisadmin/dashboard/BuyerDashboard.vue` -- `web/src/views/pisadmin/dashboard/SupplierDashboard.vue` -- `web/src/components/pisadmin/dashboard/KpiCard.vue` -- `web/src/components/pisadmin/dashboard/TrendChart.vue` -- `web/src/components/pisadmin/dashboard/MessageList.vue` -- `web/src/components/pisadmin/dashboard/BuyerTaskList.vue` -- `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue` - -**Modify:** -- `web/src/router/route.ts` - add dashboard routes - ---- - -### Task 4: Create Frontend API and Store - -- [ ] **Step 1: Create `web/src/api/pisadmin/dashboard.ts`** - -```typescript -import request from '/@/utils/request'; - -export function getDashboard() { - return request({ - url: '/api/pisadmin/dashboard/', - method: 'get', - }); -} -``` - -- [ ] **Step 2: Create `web/src/stores/modules/dashboard.ts`** - -```typescript -import { defineStore } from 'pinia'; -import { getDashboard } from '/@/api/pisadmin/dashboard'; - -interface BuyerDashboard { - kpi: { - total_inquiries: number; - pending_inquiries: number; - completed_quotes: number; - total_suppliers: number; - } | null; - tasks: Array<{ - id: number; - title: string; - inquiry_no: string; - status: string; - created_at: string; - }>; - messages: Array<{ - id: number; - title: string; - content: string; - is_read: boolean; - created_at: string; - }>; - trend: Array<{ month: string; count: number }>; -} - -interface SupplierDashboard { - kpi: { - total_quotes: number; - pending_quotes: number; - won_quotes: number; - conversion_rate: number; - } | null; - pending_quotes: Array<{ - id: number; - inquiry_no: string; - item_name: string; - quantity: number; - unit: string; - deadline: string; - }>; - messages: Array<{ - id: number; - title: string; - content: string; - is_read: boolean; - created_at: string; - }>; - trend: Array<{ month: string; quotes: number; won: number }>; -} - -export const useDashboardStore = defineStore('dashboard', { - state: () => ({ - buyer: null as BuyerDashboard | null, - supplier: null as SupplierDashboard | null, - loading: false, - }), - actions: { - async fetchDashboard() { - this.loading = true; - try { - const res: any = await getDashboard(); - this.buyer = res.data?.buyer || null; - this.supplier = res.data?.supplier || null; - } finally { - this.loading = false; - } - }, - }, -}); -``` - -- [ ] **Step 3: Commit** -```bash -git add web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts -git commit -m "feat(dashboard): add dashboard API and Pinia store" -``` - ---- - -### Task 5: Create Dashboard Components - -- [ ] **Step 1: Create `web/src/components/pisadmin/dashboard/KpiCard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 2: Create `web/src/components/pisadmin/dashboard/TrendChart.vue`** - -```vue - - - - - -``` - -- [ ] **Step 3: Create `web/src/components/pisadmin/dashboard/MessageList.vue`** - -```vue - - - - - -``` - -- [ ] **Step 4: Create `web/src/components/pisadmin/dashboard/BuyerTaskList.vue`** - -```vue - - - -``` - -- [ ] **Step 5: Create `web/src/components/pisadmin/dashboard/SupplierQuoteList.vue`** - -```vue - - - -``` - -- [ ] **Step 6: Commit** -```bash -git add web/src/components/pisadmin/dashboard/ web/src/api/pisadmin/dashboard.ts web/src/stores/modules/dashboard.ts -git commit -m "feat(dashboard): add dashboard Vue components" -``` - ---- - -### Task 6: Create Dashboard Pages - -- [ ] **Step 1: Create `web/src/views/pisadmin/dashboard/index.vue`** - -```vue - - - - - -``` - -- [ ] **Step 2: Create `web/src/views/pisadmin/dashboard/BuyerDashboard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 3: Create `web/src/views/pisadmin/dashboard/SupplierDashboard.vue`** - -```vue - - - - - -``` - -- [ ] **Step 4: Commit** -```bash -git add web/src/views/pisadmin/dashboard/ -git commit -m "feat(dashboard): add buyer/supplier dashboard pages" -``` - ---- - -### Task 7: Add Dashboard Routes - -- [ ] **Step 1: Modify `web/src/router/route.ts`** - -Add to `dynamicRoutes` array: -```typescript -{ - path: '/dashboard', - name: 'Dashboard', - component: () => import('/@/views/pisadmin/dashboard/index.vue'), - meta: { - title: 'message.router.dashboard', - isLink: '', - isHide: false, - isKeepAlive: true, - isAffix: false, - isIframe: false, - icon: 'iconfont icon-dashboard', - }, -}, -``` - -- [ ] **Step 2: Commit** -```bash -git add web/src/router/route.ts -git commit -m "feat(dashboard): add dashboard route" -``` - ---- - -## Verification Checklist - -- [ ] Backend: `GET /api/pisadmin/dashboard/` returns correct data structure -- [ ] Backend: Role-based filtering works (buyer only, supplier only, both) -- [ ] Frontend: Dashboard page loads without errors -- [ ] Frontend: KPI cards display correct data -- [ ] Frontend: Trend charts render correctly -- [ ] Frontend: Task/Quote lists display correctly -- [ ] Frontend: Role switching works for users with both roles diff --git a/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md b/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md deleted file mode 100644 index ac500ef..0000000 --- a/docs/superpowers/specs/2026-04-01-dashboard-redesign-design.md +++ /dev/null @@ -1,267 +0,0 @@ -# 控制台看板重构设计文档 - -## 1. 概述 - -### 1.1 背景 -当前系统首页使用硬编码模拟数据,需要重构为真实业务数据看板。 - -### 1.2 目标 -- 区分采购方看板和供应商看板两种视图 -- 基于用户角色动态返回对应的看板数据 -- 后端提供统一 API,前端按角色渲染 - -### 1.3 设计图 -- 采购方看板:`D:\AVC-PROJECT\docs\采购方看板.png` -- 供应商看板:`D:\AVC-PROJECT\docs\供应商看板.png` - ---- - -## 2. API 设计 - -### 2.1 接口信息 - -| 项目 | 值 | -|------|-----| -| 方法 | GET | -| 路径 | `/api/dashboard/` | -| 认证 | JWT Token | -| 响应格式 | JSON | - -### 2.2 响应结构 - -```json -{ - "buyer": { - "kpi": { - "total_inquiries": 0, - "pending_inquiries": 0, - "completed_quotes": 0, - "total_suppliers": 0 - }, - "tasks": [ - { - "id": 0, - "title": "string", - "inquiry_no": "string", - "status": "string", - "created_at": "datetime" - } - ], - "messages": [ - { - "id": 0, - "title": "string", - "content": "string", - "is_read": false, - "created_at": "datetime" - } - ], - "trend": [ - {"month": "2026-01", "count": 0} - ] - }, - "supplier": { - "kpi": { - "total_quotes": 0, - "pending_quotes": 0, - "won_quotes": 0, - "conversion_rate": 0.0 - }, - "pending_quotes": [ - { - "id": 0, - "inquiry_no": "string", - "item_name": "string", - "quantity": 0, - "unit": "string", - "deadline": "datetime" - } - ], - "messages": [], - "trend": [ - {"month": "2026-01", "quotes": 0, "won": 0} - ] - } -} -``` - -### 2.3 业务规则 -- 用户同时有采购方和供应商角色:两个模块都返回 -- 用户只有采购方角色:只返回 `buyer` 模块,`supplier` 为 `null` -- 用户只有供应商角色:只返回 `supplier` 模块,`buyer` 为 `null` - ---- - -## 3. 后端实现 - -### 3.1 应用位置 -新建 `backend/apps/pisadmin/dashboard/` 应用 - -### 3.2 数据来源 - -**采购方 KPI 计算:** -| 指标 | 数据来源 | -|------|----------| -| total_inquiries | Inquiry 表中当前用户创建的记录数 | -| pending_inquiries | Inquiry 状态为"待报价"的记录数 | -| completed_quotes | QuotationMaster 中已报价的记录数 | -| total_suppliers | Supplier 表总数 | - -**供应商 KPI 计算:** -| 指标 | 数据来源 | -|------|----------| -| total_quotes | QuotationMaster 中属于当前供应商的记录数 | -| pending_quotes | QuotationMaster 状态为"待报价"的记录数 | -| won_quotes | QuotationMaster 状态为"已中标"的记录数 | -| conversion_rate | won_quotes / total_quotes * 100 | - -**待办任务(采购方):** -- 查询条件:Inquiry 状态为"待处理",分配给当前用户 - -**待报价清单(供应商):** -- 查询条件:QuotationMaster 状态为"待报价",属于当前供应商 - -**消息通知:** -- 使用现有通知表,按时间倒序取最新 5 条 - -**趋势图数据:** -- 近 6 个月的记录创建数,按月聚合 - -### 3.3 序列化器 - -```python -# dashboard/serializers.py -class BuyerKPISerializer(serializers.Serializer): ... -class SupplierKPISerializer(serializers.Serializer): ... -class BuyerTaskSerializer(serializers.ModelSerializer): ... -class SupplierQuoteSerializer(serializers.ModelSerializer): ... -class MessageSerializer(serializers.ModelSerializer): ... -class TrendSerializer(serializers.Serializer): ... -class DashboardResponseSerializer(serializers.Serializer): ... -``` - -### 3.4 视图 - -```python -# dashboard/views.py -class DashboardView(APIView): - def get(self, request): - # 根据用户角色过滤返回数据 - ... -``` - -### 3.5 URL 配置 - -```python -# pisadmin/urls.py -path('dashboard/', include('apps.pisadmin.dashboard.urls')) -``` - ---- - -## 4. 前端实现 - -### 4.1 路由设计 - -| 路径 | 组件 | 说明 | -|------|------|------| -| `/dashboard` | `DashboardIndex.vue` | 根路由,根据角色重定向 | -| `/dashboard/buyer` | `BuyerDashboard.vue` | 采购方看板 | -| `/dashboard/supplier` | `SupplierDashboard.vue` | 供应商看板 | - -### 4.2 页面布局 - -``` -┌─────────────────────────────────────────────────┐ -│ Header: 欢迎语 + 当前日期 │ -├─────────────────────────────────────────────────┤ -│ KPI Cards (4个指标卡片横向排列) │ -├─────────────────────────────────────────────────┤ -│ Main Content (左右分栏) │ -│ ┌────────────────┐ ┌─────────────────────┐ │ -│ │ 待办/待报价清单 │ │ 趋势图 │ │ -│ │ (列表组件) │ │ (ECharts 折线图) │ │ -│ └────────────────┘ └─────────────────────┘ │ -├─────────────────────────────────────────────────┤ -│ 消息通知 (横向卡片列表) │ -└─────────────────────────────────────────────────┘ -``` - -### 4.3 组件结构 - -``` -web/src/ -├── views/ -│ └── pisadmin/ -│ └── dashboard/ -│ ├── index.vue # 重定向组件 -│ ├── BuyerDashboard.vue # 采购方看板 -│ └── SupplierDashboard.vue # 供应商看板 -├── components/ -│ └── dashboard/ -│ ├── KpiCard.vue # KPI 卡片 -│ ├── TrendChart.vue # 趋势图 -│ ├── MessageList.vue # 消息列表 -│ ├── BuyerTaskList.vue # 采购方待办 -│ └── SupplierQuoteList.vue # 供应商待报价 -└── stores/ - └── dashboard.ts # Pinia Store -``` - -### 4.4 Store 设计 - -```typescript -// stores/dashboard.ts -export const useDashboardStore = defineStore('dashboard', { - state: () => ({ - buyer: { - kpi: null, - tasks: [], - messages: [], - trend: [] - }, - supplier: { - kpi: null, - pendingQuotes: [], - messages: [], - trend: [] - }, - loading: false - }), - actions: { - async fetchDashboard() { ... } - } -}) -``` - -### 4.5 API 服务 - -```typescript -// api/dashboard.ts -export function getDashboard() { - return request.get('/api/dashboard/') -} -``` - ---- - -## 5. 权限控制 - -### 5.1 后端 -- 根据 `request.user` 的角色过滤返回数据 -- 无角色用户返回空数据 - -### 5.2 前端 -- 根据 `user.roles` 判断显示哪个看板菜单 -- 未授权用户访问看板路由时跳转至首页 - ---- - -## 6. 实现顺序 - -1. 后端 API 开发(dashboard 应用、序列化器、视图、URL) -2. 前端 API 服务和 Store -3. 通用组件(KpiCard、TrendChart、MessageList) -4. 采购方看板页面 -5. 供应商看板页面 -6. 路由配置和权限控制