diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..71c0ecd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.css linguist-language=Python +*.less linguist-language=Python +*.js linguist-language=Python +*.html linguist-language=Python diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d8c48e5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## 变更描述 + + +## 关联需求 + + +## 变更类型 +- [ ] Feature +- [ ] Bugfix +- [ ] Hotfix +- [ ] Refactor + +## 自检清单 +- [ ] 代码已通过 Pre-commit 检查(后端 ruff / 前端 lint-staged) +- [ ] 已添加必要的测试 +- [ ] 文档已更新(如需) + +## Reviewer 备注 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d44ce9a --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +/backend/venv +/backend/.idea +.idea + +.history/ +.vscode/ +web/package-lock.json + +*.bat + +# GSD planning files +.planning/ + +# Superpowers files +.claude/ +docs/superpowers/ +docs/scripts-documentation.md +.pids/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..57feb91 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,4 @@ +# Jenkins 接管 CI/CD,禁用 GitLab CI +workflow: + rules: + - when: never diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 0000000..9caba46 --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,20 @@ +## 变更描述 + + +## 关联需求 + + +## 变更类型 +- [ ] Feature +- [ ] Bugfix +- [ ] Hotfix +- [ ] Refactor +- [ ] Docs + +## 自检清单 +- [ ] 代码已通过 Pre-commit 检查(后端 ruff / 前端 lint-staged) +- [ ] 已添加必要的测试 +- [ ] 文档已更新(如需) + +## Reviewer 备注 + diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..466cbf4 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +cd web && npx lint-staged diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..23f5a47 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Django-Vue3-Admin 更新日志 + +## 正式发布v3.0.0版本 +### 1.新增:列权限管理与授权; +### 2.新增:代码新版本发布后,进行升级提醒; +### 3.优化:角色管理中按钮权限的操作; +### 4.优化:websocket 连接状态显示; +### 5.优化:初始化获取系统配置与字典配置,进行动态渲染登录页面; +### 6.修复:登录页面中系统配置不生效问题; +### 7.其他优化 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b441c6b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,112 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is **django-vue3-admin** based project with custom procurement/supplier management modules. + +- **Backend**: Django 4.2.14 + Django REST Framework +- **Frontend**: Vue 3 + TypeScript + Vite + Element Plus + FastCRUD +- **Database**: MySQL 8.0 (configured via `backend/conf/env.py`) +- **Cache/Queue**: Redis + Celery +- **Real-time**: Django Channels (WebSocket) +- **Auth**: JWT via SimpleJWT + +## Directory Structure + +``` +pis/ +├── backend/ +│ ├── application/ # Django project settings +│ │ ├── settings.py # Main settings (imports from conf/env.py) +│ │ ├── urls.py # Root URL routing +│ │ ├── asgi.py # ASGI config (WebSocket support) +│ │ ├── wsgi.py # WSGI config +│ │ └── celery.py # Celery configuration +│ ├── apps/ +│ │ ├── pisadmin/ # Custom procurement app +│ │ │ ├── basicinfo/ # Company, currency, supplier, unit, system no +│ │ │ └── miscprocurement/ # RFQ, price templates, cost sections +│ │ └── pissupplier/ # Supplier quotation module +│ ├── dvadmin/ # Core RBAC system +│ │ └── system/ # Users, roles, menus, departments, logs +│ └── conf/ +│ └── env.py # Environment configuration (DB, Redis credentials) +├── web/ # Vue 3 frontend +│ └── src/ +│ ├── views/pisadmin/ # Custom procurement pages +│ ├── views/pissupplier/# Supplier pages +│ └── views/system/ # Admin system pages +└── docker-compose.yml # Docker deployment +``` + +## Common Commands + +### Backend +```bash +cd backend + +# Make migrations for custom apps +python manage.py makemigrations apps.pisadmin.apps.pisadmin +python manage.py makemigrations apps.pissupplier + +# Apply migrations +python manage.py migrate + +# Create superuser +python manage.py init + +# Initialize area data +python manage.py init_area + +# Run development server +python manage.py runserver 0.0.0.0:8000 + +# Run with uvicorn (production-like) +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 +``` + +### Frontend +```bash +cd web +yarn install +yarn dev # Development server (port 8080) +yarn build # Production build +yarn build:dev # Development build +yarn lint-fix # Lint and fix +``` + +### Docker +```bash +docker-compose up -d +docker exec dvadmin3-django python manage.py makemigrations +docker exec dvadmin3-django python manage.py migrate +docker exec dvadmin3-django python manage.py init +``` + +## Configuration + +Backend settings are split: +- `application/settings.py` - Django defaults +- `conf/env.py` - Environment-specific config (database, Redis, secrets) + +## Key Dependencies + +**Backend**: Django 4.2.14, djangorestframework 3.15.2, channels 4.1.0, celery, SimpleJWT + +**Frontend**: Vue 3.4, Element Plus 2.8, FastCRUD 1.21, Vite 5.4, Pinia, Vue Router 4.4 + +## Notes + +- Custom apps (`pisadmin`, `pissupplier`) extend the base `dvadmin` RBAC system +- The `miscprocurement` app handles询价 (RFQ) and cost template building +- Supplier quotations are managed in `pissupplier` app +- Default demo credentials: `superadmin` / `admin123456` + +## Git 注意事项 + +**不要提交本地文档和 superpowers 相关文件**: +- `.claude/` 目录下的 superpowers 相关文件(`.planning/`、skills 等) +- `docs/` 目录下的本地文档 +- 这些文件每个人开发任务不同,不应提交到仓库 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..951908f --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2022] [django-vue-admin] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b506438 --- /dev/null +++ b/Makefile @@ -0,0 +1,206 @@ +# ============================================================ +# PIS Project Startup Script - Makefile +# PIS 项目启动脚本 +# ============================================================ +# 使用方法: +# make help 查看所有可用命令 +# make dev 启动开发环境(前端 + 后端 + Celery) +# make dev-backend 仅启动后端 +# make dev-frontend 仅启动前端 +# make dev-celery 仅启动 Celery +# make stop 停止所有服务 +# make status 查看服务状态 +# make logs 查看所有日志 +# make logs-backend 查看后端日志 +# make logs-frontend 查看前端日志 +# make logs-celery 查看 Celery 日志 +# make restart 重启所有服务 +# make restart-celery 重启 Celery +# ============================================================ + +# 默认环境 +ENV ?= dev + +# 路径配置 +PROJECT_ROOT := $(shell pwd) +BACKEND_DIR := $(PROJECT_ROOT)/backend +FRONTEND_DIR := $(PROJECT_ROOT)/web +SCRIPTS_DIR := $(PROJECT_ROOT)/scripts +LOG_DIR := $(BACKEND_DIR)/logs + +# PID 文件目录 +PID_DIR := $(PROJECT_ROOT)/.pids + +# 后端服务端口 +BACKEND_PORT := 8000 +FRONTEND_PORT := 8080 + +# 帮助信息 +.PHONY: help +help: + @echo "" + @echo "========================================" + @echo " PIS 项目启动脚本 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 启动命令:" + @echo " make dev 启动开发环境(前端+后端+Celery)" + @echo " make dev-backend 仅启动后端服务" + @echo " make dev-frontend 仅启动前端服务" + @echo " make dev-celery 仅启动 Celery" + @echo "" + @echo " 停止/重启:" + @echo " make stop 停止所有服务" + @echo " make restart 重启所有服务" + @echo " make restart-celery 重启 Celery" + @echo "" + @echo " 日志查看:" + @echo " make logs 查看所有日志" + @echo " make logs-backend 查看后端日志" + @echo " make logs-frontend 查看前端日志" + @echo " make logs-celery 查看 Celery 日志" + @echo "" + @echo " 状态查看:" + @echo " make status 查看所有服务状态" + @echo "" + @echo " 环境切换:" + @echo " ENV=prod make dev 使用生产环境配置启动" + @echo "" + @echo "========================================" + +# ============================================================ +# 创建必要的目录 +# ============================================================ +$(PID_DIR): + @mkdir -p $(PID_DIR) + +$(LOG_DIR): + @mkdir -p $(LOG_DIR) + +# ============================================================ +# 启动开发环境 - 全部服务 +# ============================================================ +.PHONY: dev +dev: $(PID_DIR) $(LOG_DIR) + @echo "========================================" + @echo " 启动开发环境 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 后端: http://localhost:$(BACKEND_PORT)" + @echo " 前端: http://localhost:$(FRONTEND_PORT)" + @echo " Celery: Worker + Beat" + @echo "" + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 启动后端 +# ============================================================ +.PHONY: dev-backend +dev-backend: $(PID_DIR) $(LOG_DIR) + @echo "启动后端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + +# ============================================================ +# 启动前端 +# ============================================================ +.PHONY: dev-frontend +dev-frontend: + @echo "启动前端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + +# ============================================================ +# 启动 Celery +# ============================================================ +.PHONY: dev-celery +dev-celery: $(PID_DIR) $(LOG_DIR) + @echo "启动 Celery (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 停止所有服务 +# ============================================================ +.PHONY: stop +stop: + @echo "停止所有服务..." + @bash $(SCRIPTS_DIR)/stop-all.sh + @echo "所有服务已停止" + +# ============================================================ +# 重启所有服务 +# ============================================================ +.PHONY: restart +restart: + @echo "重启所有服务..." + @$(MAKE) stop + @sleep 2 + @$(MAKE) dev + +# ============================================================ +# 重启 Celery +# ============================================================ +.PHONY: restart-celery +restart-celery: + @bash $(SCRIPTS_DIR)/stop-celery.sh + @sleep 1 + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 查看日志 +# ============================================================ +.PHONY: logs +logs: + @echo "========================================" + @echo " 查看所有日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh all + +.PHONY: logs-backend +logs-backend: + @echo "========================================" + @echo " 查看后端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh backend + +.PHONY: logs-frontend +logs-frontend: + @echo "========================================" + @echo " 查看前端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh frontend + +.PHONY: logs-celery +logs-celery: + @echo "========================================" + @echo " 查看 Celery 日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh celery + +# ============================================================ +# 查看服务状态 +# ============================================================ +.PHONY: status +status: + @echo "========================================" + @echo " 服务状态检查" + @echo "========================================" + @bash $(SCRIPTS_DIR)/check-status.sh + +# ============================================================ +# 清理日志 +# ============================================================ +.PHONY: clean-logs +clean-logs: + @echo "清理日志文件..." + @find $(LOG_DIR) -type f -name "*.log" -exec rm -f {} \; 2>/dev/null || true + @echo "日志已清理" + +# ============================================================ +# 清理 PID 文件(强制停止) +# ============================================================ +.PHONY: clean-pids +clean-pids: + @echo "清理 PID 文件..." + @rm -f $(PID_DIR)/*.pid 2>/dev/null || true + @echo "PID 文件已清理" diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..07cb07b --- /dev/null +++ b/NOTICE @@ -0,0 +1,13 @@ +Copyright 2021 李强 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..a2f82f6 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,192 @@ +# PIS - 采购与供应商管理系统 + +## 项目简介 + +基于 Django-Vue3 的采购与供应商管理系统,采用 RBAC 权限控制。 + +## 项目架构 🏗️ + +### 技术栈 + +| 层级 | 技术 | +|------|------| +| **后端** | Django 4.2.14 + Django REST Framework 3.15.2 | +| **前端** | Vue 3 + TypeScript + Vite 5.4 + Element Plus 2.8 + FastCRUD | +| **数据库** | MySQL 8.0 | +| **缓存/队列** | Redis + Celery | +| **实时通信** | Django Channels (WebSocket) | +| **认证** | JWT via SimpleJWT | + +### 目录结构 + +``` +pis/ +├── backend/ +│ ├── application/ # Django 项目配置 +│ │ ├── settings.py # 主配置(从 conf/env.py 导入配置) +│ │ ├── urls.py # 根路由 +│ │ ├── asgi.py # ASGI 配置(支持 WebSocket) +│ │ ├── wsgi.py # WSGI 配置 +│ │ └── celery.py # Celery 配置 +│ ├── apps/ +│ │ ├── pisadmin/ # 采购管理应用 +│ │ │ ├── basicinfo/ # 基础信息:公司、币种、供应商、单位、编码规则 +│ │ │ └── miscprocurement/ # 杂项采购:询价、价格模板、成本节 +│ │ └── pissupplier/ # 供应商报价模块 +│ ├── dvadmin/ # 核心 RBAC 系统 +│ │ └── system/ # 用户、角色、菜单、部门、日志 +│ └── conf/ +│ └── env.py # 环境配置(数据库、Redis 凭证) +├── web/ # Vue 3 前端 +│ └── src/ +│ ├── views/pisadmin/ # 采购管理页面 +│ ├── views/pissupplier/# 供应商页面 +│ └── views/system/ # 系统管理页面 +└── scripts/ # 服务管理脚本 + ├── start-backend.sh # 启动后端服务 + ├── start-frontend.sh # 启动前端服务 + ├── start-celery.sh # 启动 Celery worker/beat + ├── stop-all.sh # 停止所有服务 + └── check-status.sh # 查看服务状态 +``` + +### 模块说明 + +| 模块 | 说明 | +|------|------| +| `pisadmin/basicinfo` | 基础信息管理:公司、币种、供应商、单位、系统编号规则 | +| `pisadmin/miscprocurement` | 杂项采购:询价(RFQ)、价格模板、成本节构建器 | +| `pissupplier` | 供应商报价模块 | +| `dvadmin/system` | 核心 RBAC 系统:用户、角色、菜单、部门、操作日志 | + +### 配置说明 + +后端配置分离: +- `application/settings.py` - Django 默认配置 +- `conf/env.py` - 环境特定配置(数据库、Redis、密钥) + +## 准备工作 + +~~~ +Python >= 3.11.0 (最低3.9+版本) +nodejs >= 16.0 +Mysql >= 8.0 (可选,默认数据库sqlite3,支持5.7+,推荐8.0版本) +Redis (可选,最新版) +~~~ + +## 快速启动 + +### 后端 + +#### 方式一:脚本启动(推荐) + +```bash +# 进入项目根目录 +cd D:\AVC-PROJECT\pis + +# 启动后端服务(开发模式) +./scripts/start-backend.sh + +# 启动前端服务 +./scripts/start-frontend.sh + +# 启动 Celery 异步任务(可选) +./scripts/start-celery.sh +``` + +**脚本功能说明:** + +| 脚本 | 说明 | +|------|------| +| `start-backend.sh` | 启动后端服务(自动创建 PID 和日志文件)| +| `start-frontend.sh` | 启动前端开发服务器 | +| `start-celery.sh` | 启动 Celery Worker 和 Beat | +| `stop-all.sh` | 停止所有服务 | +| `stop-celery.sh` | 仅停止 Celery 服务 | +| `check-status.sh` | 查看所有服务状态 | +| `view-logs.sh` | 查看服务日志 | + +**查看日志:** +```bash +./scripts/view-logs.sh all # 查看所有日志 +./scripts/view-logs.sh backend # 仅看后端日志 +./scripts/view-logs.sh celery # 仅看 Celery 日志 +``` + +**停止服务:** +```bash +./scripts/stop-all.sh # 停止所有服务 +./scripts/stop-celery.sh # 仅停止 Celery +``` + +#### 方式二:手动启动 + +```bash +# 1. 进入后端目录 +cd backend + +# 2. 复制并配置环境文件 +cp ./conf/env.example.py ./conf/env.py +# 编辑 env.py 配置数据库信息 + +# 3. 安装依赖 +pip3 install -r requirements.txt + +# 4. 执行数据库迁移 +python3 manage.py makemigrations +python3 manage.py migrate + +# 5. 初始化数据 +python3 manage.py init +python3 manage.py init_area + +# 6. 启动后端 +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8 +``` + +### 前端 + +```bash +# 进入前端目录 +cd web + +# 安装依赖 +npm install yarn +yarn install --registry=https://registry.npmmirror.com + +# 启动开发服务器 +yarn run dev +# 浏览器访问 http://localhost:8080 +# .env.development 文件中可配置启动端口等参数 + +# 构建生产环境 +yarn run build +``` + +### 访问项目 + +- 前端地址:http://localhost:8080 +- 后端接口:http://localhost:8080/api +- 默认账号:`superadmin` / `admin123456` + +### Docker 部署 + +```shell +docker-compose up -d +# 初始化后端数据(第一次执行即可) +docker exec -ti dvadmin3-django bash +python manage.py makemigrations +python manage.py migrate +python manage.py init_area +python manage.py init +exit + +# 停止服务 +docker-compose down + +# 重启服务 +docker-compose restart + +# 重新构建并启动 +docker-compose up -d --build +``` diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9857f2b --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,100 @@ +# Byte-compiled / optimized / DLL files +*.py[cod] +*$py.class +__pycache__/ +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +.idea/ +*.db +.DS_Store +*.pyc +conf/* +!conf/env.example.py +!conf/env.py +db.sqlite3 +media/ +__pypackages__/ +package-lock.json +gunicorn.pid +!plugins/__init__.py diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml new file mode 100644 index 0000000..bdbf3cd --- /dev/null +++ b/backend/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# 后端 pre-commit hooks 配置 +# 安装方式:pip install pre-commit && pre-commit install +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: ['--maxkb=5000'] + - id: check-merge-conflict + - id: debug-statements + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.0 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..d8b5393 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,52 @@ +# 第一阶段:构建 +FROM docker.1ms.run/python:3.10-slim as builder + +WORKDIR /backend + +# 安装系统依赖(合并为一个RUN减少层数) +RUN apt-get update && apt-get install -y \ + bash git curl gnupg ca-certificates \ + libjpeg-dev libmariadb-dev-compat default-libmysqlclient-dev \ + build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev \ + libpcre2-dev libncurses5-dev libreadline-dev tk-dev libpq-dev unixodbc-dev \ + && rm -rf /var/lib/apt/lists/* \ + && curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg \ + && echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-focal-prod focal main" > /etc/apt/sources.list.d/mssql-release.list \ + && apt-get update \ + && ACCEPT_EULA=Y apt-get install -y msodbcsql18 \ + && rm -rf /var/lib/apt/lists/* + +# 先复制依赖文件,利用缓存 +COPY ./backend/requirements.txt . + +# 安装 Python 依赖(带缓存加速) +RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt + +# 复制剩余代码 +COPY ./backend/ . + +# 配置 +RUN ls ./conf/ \ + && awk 'BEGIN { cmd="cp -i ./conf/env.example.py ./conf/env.py "; print "n" |cmd; }' \ + && sed -i "s|DATABASE_HOST = '127.0.0.1'|DATABASE_HOST = '177.10.0.1'|g" ./conf/env.py \ + && sed -i "s|REDIS_HOST = '127.0.0.1'|REDIS_HOST = '177.10.0.1'|g" ./conf/env.py + +# 第二阶段:运行时镜像 +FROM docker.1ms.run/python:3.10-slim + +RUN apt-get update && apt-get install -y \ + unixodbc libodbc2 \ + libssl3 libkrb5-3 krb5-user \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /opt/microsoft/msodbcsql18 /opt/microsoft/msodbcsql18 +COPY --from=builder /backend /backend + +# 设置库路径并注册 ODBC Driver 18 +ENV LD_LIBRARY_PATH=/opt/microsoft/msodbcsql18/lib64:${LD_LIBRARY_PATH} +RUN cat /opt/microsoft/msodbcsql18/etc/odbcinst.ini >> /etc/odbcinst.ini || true + +WORKDIR /backend +CMD ["sh","docker_start.sh"] diff --git a/backend/Jenkinsfile b/backend/Jenkinsfile new file mode 100644 index 0000000..1ebab16 --- /dev/null +++ b/backend/Jenkinsfile @@ -0,0 +1,60 @@ +pipeline { + agent any + + environment { + HARBOR_URL = '192.168.80.90:8090' + HARBOR_PROJECT = 'pis' + IMAGE_NAME = 'pis-backend' + CREDENTIALS_ID = 'harbor-credentials' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build Image') { + steps { + script { + def imageTag = "${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:${BUILD_NUMBER}" + env.IMAGE_TAG = imageTag + sh """ + docker build -f backend/Dockerfile \ + -t ${imageTag} . + """ + } + } + } + + stage('Push to Harbor') { + steps { + script { + withCredentials([usernamePassword( + credentialsId: CREDENTIALS_ID, + usernameVariable: 'HARBOR_USER', + passwordVariable: 'HARBOR_PASS' + )]) { + sh """ + echo ${HARBOR_PASS} | docker login ${HARBOR_URL} -u ${HARBOR_USER} --password-stdin + docker push ${IMAGE_TAG} || true + docker tag ${IMAGE_TAG} ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest + docker push ${HARBOR_URL}/${HARBOR_PROJECT}/${IMAGE_NAME}:latest || true + docker logout ${HARBOR_URL} + """ + } + } + } + } + } + + post { + failure { + echo "Backend Pipeline failed! Check Jenkins logs." + } + success { + echo "Backend image pushed to Harbor: ${IMAGE_TAG}" + } + } +} diff --git a/backend/application/__init__.py b/backend/application/__init__.py new file mode 100644 index 0000000..1e3599b --- /dev/null +++ b/backend/application/__init__.py @@ -0,0 +1,5 @@ +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/backend/application/asgi.py b/backend/application/asgi.py new file mode 100644 index 0000000..2582f54 --- /dev/null +++ b/backend/application/asgi.py @@ -0,0 +1,32 @@ +""" +ASGI config for application project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.security.websocket import AllowedHostsOriginValidator +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + +http_application = get_asgi_application() +from application.ws_routing import websocket_urlpatterns + +application = ProtocolTypeRouter({ + "http": http_application, + 'websocket': AllowedHostsOriginValidator( + AuthMiddlewareStack( + URLRouter( + websocket_urlpatterns # 指明路由文件是devops/routing.py + ) + ) + ), +}) diff --git a/backend/application/celery.py b/backend/application/celery.py new file mode 100644 index 0000000..c719c46 --- /dev/null +++ b/backend/application/celery.py @@ -0,0 +1,51 @@ +import functools +import os + +from celery.signals import task_postrun + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') + +from django.conf import settings +from celery import platforms + +# 租户模式 +if "django_tenants" in settings.INSTALLED_APPS: + from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp + + app = TenantAwareCeleryApp() +else: + from celery import Celery + + app = Celery(f"application") +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) +platforms.C_FORCE_ROOT = True + + +def retry_base_task_error(): + """ + celery 失败重试装饰器 + :return: + """ + + def wraps(func): + @app.task(bind=True, retry_delay=180, max_retries=3) + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as exc: + raise self.retry(exc=exc) + + return wrapper + + return wraps + + +@task_postrun.connect +def add_periodic_task_name(sender, task_id, task, args, kwargs, **extras): + periodic_task_name = kwargs.get('periodic_task_name') + if periodic_task_name: + from django_celery_results.models import TaskResult + # 更新 TaskResult 表中的 periodic_task_name 字段 + TaskResult.objects.filter(task_id=task_id).update(periodic_task_name=periodic_task_name) diff --git a/backend/application/dispatch.py b/backend/application/dispatch.py new file mode 100644 index 0000000..101b3ab --- /dev/null +++ b/backend/application/dispatch.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from django.conf import settings +from django.db import connection +from django.core.cache import cache +from dvadmin.utils.validator import CustomValidationError + +dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis + + +def is_tenants_mode(): + """ + 判断是否为租户模式 + :return: + """ + return hasattr(connection, "tenant") and connection.tenant.schema_name + + +# ================================================= # +# ******************** 初始化 ******************** # +# ================================================= # +def _get_all_dictionary(): + from dvadmin.system.models import Dictionary + + queryset = Dictionary.objects.filter(status=True, is_value=False) + data = [] + for instance in queryset: + data.append( + { + "id": instance.id, + "value": instance.value, + "children": list( + Dictionary.objects.filter(parent=instance.id) + .filter(status=1) + .values("label", "value", "type", "color") + ), + } + ) + return {ele.get("value"): ele for ele in data} + + +def _get_all_system_config(): + data = {} + from dvadmin.system.models import SystemConfig + + system_config_obj = ( + SystemConfig.objects.filter(parent_id__isnull=False) + .values("parent__key", "key", "value", "form_item_type") + .order_by("sort") + ) + for system_config in system_config_obj: + value = system_config.get("value", "") + if value and system_config.get("form_item_type") == 7: + value = value[0].get("url") + if value and system_config.get("form_item_type") == 11: + new_value = [] + for ele in value: + new_value.append({ + "key": ele.get('key'), + "title": ele.get('title'), + "value": ele.get('value'), + }) + new_value.sort(key=lambda s: s["key"]) + value = new_value + data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value + return data + + +def init_dictionary(): + """ + 初始化字典配置 + :return: + """ + try: + if dispatch_db_type == 'redis': + cache.set(f"init_dictionary", _get_all_dictionary()) + return + if is_tenants_mode(): + from django_tenants.utils import tenant_context, get_tenant_model + + for tenant in get_tenant_model().objects.filter(): + with tenant_context(tenant): + settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary() + else: + settings.DICTIONARY_CONFIG = _get_all_dictionary() + except Exception as e: + print("请先进行数据库迁移!") + return + + +def init_system_config(): + """ + 初始化系统配置 + :param name: + :return: + """ + try: + if dispatch_db_type == 'redis': + cache.set(f"init_system_config", _get_all_system_config()) + return + if is_tenants_mode(): + from django_tenants.utils import tenant_context, get_tenant_model + + for tenant in get_tenant_model().objects.filter(): + with tenant_context(tenant): + settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config() + else: + settings.SYSTEM_CONFIG = _get_all_system_config() + except Exception as e: + print("请先进行数据库迁移!") + return + + +def refresh_dictionary(): + """ + 刷新字典配置 + :return: + """ + if dispatch_db_type == 'redis': + cache.set(f"init_dictionary", _get_all_dictionary()) + return + if is_tenants_mode(): + from django_tenants.utils import tenant_context, get_tenant_model + + for tenant in get_tenant_model().objects.filter(): + with tenant_context(tenant): + settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary() + else: + settings.DICTIONARY_CONFIG = _get_all_dictionary() + + +def refresh_system_config(): + """ + 刷新系统配置 + :return: + """ + if dispatch_db_type == 'redis': + cache.set(f"init_system_config", _get_all_system_config()) + return + if is_tenants_mode(): + from django_tenants.utils import tenant_context, get_tenant_model + + for tenant in get_tenant_model().objects.filter(): + with tenant_context(tenant): + settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config() + else: + settings.SYSTEM_CONFIG = _get_all_system_config() + + +# ================================================= # +# ******************** 字典管理 ******************** # +# ================================================= # +def get_dictionary_config(schema_name=None): + """ + 获取字典所有配置 + :param schema_name: 对应字典配置的租户schema_name值 + :return: + """ + if dispatch_db_type == 'redis': + init_dictionary_data = cache.get(f"init_dictionary") + if not init_dictionary_data: + refresh_dictionary() + return cache.get(f"init_dictionary") or {} + if not settings.DICTIONARY_CONFIG: + refresh_dictionary() + if is_tenants_mode(): + dictionary_config = settings.DICTIONARY_CONFIG[schema_name or connection.tenant.schema_name] + else: + dictionary_config = settings.DICTIONARY_CONFIG + return dictionary_config or {} + + +def get_dictionary_values(key, schema_name=None): + """ + 获取字典数据数组 + :param key: 对应字典配置的key值(字典编号) + :param schema_name: 对应字典配置的租户schema_name值 + :return: + """ + if dispatch_db_type == 'redis': + dictionary_config = cache.get(f"init_dictionary") + if not dictionary_config: + refresh_dictionary() + dictionary_config = cache.get(f"init_dictionary") + return dictionary_config.get(key) + dictionary_config = get_dictionary_config(schema_name) + return dictionary_config.get(key) + + +def get_dictionary_label(key, name, schema_name=None): + """ + 获取获取字典label值 + :param key: 字典管理中的key值(字典编号) + :param name: 对应字典配置的value值 + :param schema_name: 对应字典配置的租户schema_name值 + :return: + """ + res = get_dictionary_values(key, schema_name) or [] + for ele in res.get('children'): + if ele.get("value") == str(name): + return ele.get("label") + return "" + + +# ================================================= # +# ******************** 系统配置 ******************** # +# ================================================= # +def get_system_config(schema_name=None): + """ + 获取系统配置中所有配置 + 1.只传父级的key,返回全部子级,{ "父级key.子级key" : "值" } + 2."父级key.子级key",返回子级值 + :param schema_name: 对应字典配置的租户schema_name值 + :return: + """ + if dispatch_db_type == 'redis': + init_dictionary_data = cache.get(f"init_system_config") + if not init_dictionary_data: + refresh_system_config() + return cache.get(f"init_system_config") or {} + if not settings.SYSTEM_CONFIG: + refresh_system_config() + if is_tenants_mode(): + dictionary_config = settings.SYSTEM_CONFIG[schema_name or connection.tenant.schema_name] + else: + dictionary_config = settings.SYSTEM_CONFIG + return dictionary_config or {} + + +def get_system_config_values(key, schema_name=None): + """ + 获取系统配置数据数组 + :param key: 对应系统配置的key值(字典编号) + :param schema_name: 对应系统配置的租户schema_name值 + :return: + """ + if dispatch_db_type == 'redis': + system_config = cache.get(f"init_system_config") + if not system_config: + refresh_system_config() + system_config = cache.get(f"init_system_config") + return system_config.get(key) + system_config = get_system_config(schema_name) + return system_config.get(key) + + +def get_system_config_values_to_dict(key, schema_name=None): + """ + 获取系统配置数据并转换为字典 **仅限于数组类型系统配置 + :param key: 对应系统配置的key值(字典编号) + :param schema_name: 对应系统配置的租户schema_name值 + :return: + """ + values_dict = {} + config_values = get_system_config_values(key, schema_name) + if not isinstance(config_values, list): + raise CustomValidationError("该方式仅限于数组类型系统配置") + for ele in get_system_config_values(key, schema_name): + values_dict[ele.get('key')] = ele.get('value') + return values_dict + + +def get_system_config_label(key, name, schema_name=None): + """ + 获取获取系统配置label值 + :param key: 系统配置中的key值(字典编号) + :param name: 对应系统配置的value值 + :param schema_name: 对应系统配置的租户schema_name值 + :return: + """ + children = get_system_config_values(key, schema_name) or [] + for ele in children: + if ele.get("value") == str(name): + return ele.get("label") + return "" diff --git a/backend/application/settings.py b/backend/application/settings.py new file mode 100644 index 0000000..e9e961a --- /dev/null +++ b/backend/application/settings.py @@ -0,0 +1,437 @@ +""" +Django settings for application project. + +Generated by 'django-admin startproject' using Django 3.2.3. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +import os +import sys +from pathlib import Path +from datetime import timedelta + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# ================================================= # +# ******************** 动态配置 ******************** # +# ================================================= # + +from conf.env import * + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure--z8%exyzt7e_%i@1+#1mm=%lb5=^fx_57=1@a+_y7bg5-w%)sm" +# 初始化plugins插件路径到环境变量中 +PLUGINS_PATH = os.path.join(BASE_DIR, "plugins") +sys.path.insert(0, os.path.join(PLUGINS_PATH)) + +[ + sys.path.insert(0, os.path.join(PLUGINS_PATH, ele)) + for ele in os.listdir(PLUGINS_PATH) + if os.path.isdir(os.path.join(PLUGINS_PATH, ele)) and not ele.startswith("__") +] + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = locals().get("DEBUG", True) +ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"]) + +# 列权限需要排除的App应用 +COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", []) + +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_comment_migrate", + "rest_framework", + "django_filters", + "corsheaders", # 注册跨域app + "drf_yasg", + "captcha", + "channels", + "dvadmin.system", + "apps.pisadmin.miscprocurement", + "apps.pisadmin.basicinfo", + "apps.pisadmin.dashboard", + "apps.pissupplier", + "sync", +] + +MIDDLEWARE = [ + "dvadmin.utils.middleware.HealthCheckMiddleware", + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", # 跨域中间件 + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "dvadmin.utils.middleware.ApiLoggingMiddleware", +] + +ROOT_URLCONF = "application.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "application.wsgi.application" + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": DATABASE_ENGINE, + "NAME": DATABASE_NAME, + "USER": DATABASE_USER, + "PASSWORD": DATABASE_PASSWORD, + "HOST": DATABASE_HOST, + "PORT": DATABASE_PORT, + "OPTIONS": { + "driver": "ODBC Driver 18 for SQL Server", + "extra_params": "Encrypt=yes;TrustServerCertificate=yes" + } + } +} +AUTH_USER_MODEL = "system.Users" +USERNAME_FIELD = "username" + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = "zh-hans" + +TIME_ZONE = "Asia/Shanghai" + +USE_I18N = True + +USE_L10N = True + +USE_TZ = False + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = "/static/" +# # 设置django的静态文件目录 +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] + +MEDIA_ROOT = "media" # 项目下的目录 +MEDIA_URL = "/media/" # 跟STATIC_URL类似,指定用户可以通过这个url找到文件 + +#添加以下代码以后就不用写{% load staticfiles %},可以直接引用 +STATICFILES_FINDERS = ( + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder" +) +# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释 +# python manage.py collectstatic +# STATIC_ROOT=os.path.join(BASE_DIR,'static') + +# ================================================= # +# ******************* 跨域的配置 ******************* # +# ================================================= # + +# 全部允许配置 +CORS_ORIGIN_ALLOW_ALL = True +# 允许cookie +CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作 + +# ===================================================== # +# ********************* channels配置 ******************* # +# ===================================================== # +ASGI_APPLICATION = 'application.asgi.application' +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels.layers.InMemoryChannelLayer" + } +} +# CHANNEL_LAYERS = { +# 'default': { +# 'BACKEND': 'channels_redis.core.RedisChannelLayer', +# 'CONFIG': { +# "hosts": [('127.0.0.1', 6379)], #需修改 +# }, +# }, +# } + + +# ================================================= # +# ********************* 日志配置 ******************* # +# ================================================= # +# # log 配置部分BEGIN # +SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log") +ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log") +LOGS_FILE = os.path.join(BASE_DIR, "logs") +if not os.path.exists(os.path.join(BASE_DIR, "logs")): + os.makedirs(os.path.join(BASE_DIR, "logs")) + +# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志: +# 格式:[日期][模块.函数名称():行号] [级别] 信息 +STANDARD_LOG_FORMAT = ( + "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s" +) +CONSOLE_LOG_FORMAT = ( + "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s" +) +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": {"format": STANDARD_LOG_FORMAT}, + "console": { + "format": CONSOLE_LOG_FORMAT, + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + "file": { + "format": CONSOLE_LOG_FORMAT, + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + }, + "handlers": { + "file": { + "level": "INFO", + "class": "logging.handlers.RotatingFileHandler", + "filename": SERVER_LOGS_FILE, + "maxBytes": 1024 * 1024 * 100, # 100 MB + "backupCount": 5, # 最多备份5个 + "formatter": "standard", + "encoding": "utf-8", + }, + "error": { + "level": "ERROR", + "class": "logging.handlers.RotatingFileHandler", + "filename": ERROR_LOGS_FILE, + "maxBytes": 1024 * 1024 * 100, # 100 MB + "backupCount": 3, # 最多备份3个 + "formatter": "standard", + "encoding": "utf-8", + }, + "console": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "console", + }, + + }, + "loggers": { + "": { + "handlers": ["console", "error", "file"], + "level": "INFO", + }, + "django": { + "handlers": ["console", "error", "file"], + "level": "INFO", + "propagate": False, + }, + 'django.db.backends': { + 'handlers': ["console", "error", "file"], + 'propagate': False, + 'level': "INFO" + }, + "uvicorn.error": { + "level": "INFO", + "handlers": ["console", "error", "file"], + }, + "uvicorn.access": { + "handlers": ["console", "error", "file"], + "level": "INFO" + }, + }, +} + +# ================================================= # +# *************** REST_FRAMEWORK配置 *************** # +# ================================================= # + +REST_FRAMEWORK = { + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.MultiPartParser', + ), + "DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 日期时间格式配置 + "DATE_FORMAT": "%Y-%m-%d", + "DEFAULT_FILTER_BACKENDS": ( + # 'django_filters.rest_framework.DjangoFilterBackend', + "dvadmin.utils.filters.CustomDjangoFilterBackend", + "rest_framework.filters.SearchFilter", + "rest_framework.filters.OrderingFilter", + ), + "DEFAULT_PAGINATION_CLASS": "dvadmin.utils.pagination.CustomPagination", # 自定义分页 + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + "rest_framework.authentication.SessionAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", # 只有经过身份认证确定用户身份才能访问 + ], + "EXCEPTION_HANDLER": "dvadmin.utils.exception.CustomExceptionHandler", # 自定义的异常处理 +} +# ================================================= # +# ******************** 登录方式配置 ******************** # +# ================================================= # + +AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"] +# ================================================= # +# ****************** simplejwt配置 ***************** # +# ================================================= # +SIMPLE_JWT = { + # token有效时长 + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440), + # token刷新后的有效时间 + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + # 设置前缀 + "AUTH_HEADER_TYPES": ("JWT",), + "ROTATE_REFRESH_TOKENS": True, +} + +# ====================================# +# ****************swagger************# +# ====================================# +SWAGGER_SETTINGS = { + # 基础样式 + "SECURITY_DEFINITIONS": {"basic": {"type": "basic"}}, + # 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的. + "LOGIN_URL": "apiLogin/", + # 'LOGIN_URL': 'rest_framework:login', + "LOGOUT_URL": "rest_framework:logout", + # 'DOC_EXPANSION': None, + # 'SHOW_REQUEST_HEADERS':True, + # 'USE_SESSION_AUTH': True, + # 'DOC_EXPANSION': 'list', + # 接口文档中方法列表以首字母升序排列 + "APIS_SORTER": "alpha", + # 如果支持json提交, 则接口文档中包含json输入框 + "JSON_EDITOR": True, + # 方法列表字母排序 + "OPERATIONS_SORTER": "alpha", + "VALIDATOR_URL": None, + "AUTO_SCHEMA_TYPE": 2, # 分组根据url层级分,0、1 或 2 层 + "DEFAULT_AUTO_SCHEMA_CLASS": "dvadmin.utils.swagger.CustomSwaggerAutoSchema", +} + +# ================================================= # +# **************** 验证码配置 ******************* # +# ================================================= # +CAPTCHA_IMAGE_SIZE = (160, 46) # 设置 captcha 图片大小 +CAPTCHA_LENGTH = 4 # 字符个数 +CAPTCHA_TIMEOUT = 1 # 超时(minutes) +CAPTCHA_OUTPUT_FORMAT = "%(image)s %(text_field)s %(hidden_field)s " +CAPTCHA_FONT_SIZE = 36 # 字体大小 +CAPTCHA_FOREGROUND_COLOR = "#64DAAA" # 前景色 +CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色 +CAPTCHA_NOISE_FUNCTIONS = ( + "captcha.helpers.noise_arcs", # 线 + # "captcha.helpers.noise_dots", # 点 +) +# CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' #字母验证码 +CAPTCHA_CHALLENGE_FUNCT = "captcha.helpers.math_challenge" # 加减乘除验证码 + +# ================================================= # +# ******************** 其他配置 ******************** # +# ================================================= # + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +API_LOG_ENABLE = True +# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE'] +API_LOG_METHODS = ["POST", "UPDATE", "DELETE", "PUT"] # ['POST', 'DELETE'] +API_MODEL_MAP = { + "/token/": "登录模块", + "/api/login/": "登录模块", + "/api/plugins_market/plugins/": "插件市场", +} + +DJANGO_CELERY_BEAT_TZ_AWARE = False +from celery.schedules import crontab + +CELERY_TIMEZONE = "Asia/Shanghai" # celery 时区问题 + +CELERY_BEAT_SCHEDULE = { + "check-quote-deadline-expired": { + "task": "apps.pisadmin.miscprocurement.tasks.check_quote_deadline_expired", + "schedule": crontab(minute="*/5"), # 每5分钟 + }, +} + +# 静态页面压缩 +STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage" + +ALL_MODELS_OBJECTS = [] # 所有app models 对象 + +# 初始化需要执行的列表,用来初始化后执行 +INITIALIZE_LIST = [] +INITIALIZE_RESET_LIST = [] +# 表前缀 +TABLE_PREFIX = locals().get('TABLE_PREFIX', "") +# 系统配置 +SYSTEM_CONFIG = {} +# 字典配置 +DICTIONARY_CONFIG = {} + +# ================================================= # +# ******************** 插件配置 ******************** # +# ================================================= # +# 租户共享app +TENANT_SHARED_APPS = [] +# 普通租户独有app +TENANT_EXCLUSIVE_APPS = [] +# 插件 urlpatterns +PLUGINS_URL_PATTERNS = [] +# 所有模式有的 +SHARED_APPS = [] +# ********** 一键导入插件配置开始 ********** +# 例如: +# from dvadmin_upgrade_center.settings import * # 升级中心 +from dvadmin3_celery.settings import * # celery 异步任务 +# from dvadmin_third.settings import * # 第三方用户管理 +from dvadmin_ak_sk.settings import * # 秘钥管理管理 +# from dvadmin_tenants.settings import * # 租户管理 +#from dvadmin_social_auth.settings import * +#from dvadmin_uniapp.settings import * +# ... +# ********** 一键导入插件配置结束 ********** diff --git a/backend/application/sse_views.py b/backend/application/sse_views.py new file mode 100644 index 0000000..f1cbe01 --- /dev/null +++ b/backend/application/sse_views.py @@ -0,0 +1,33 @@ +# views.py +import time + +import jwt +from django.http import StreamingHttpResponse + +from application import settings +from dvadmin.system.models import MessageCenterTargetUser +from django.core.cache import cache + + +def event_stream(user_id): + last_sent_time = 0 + + while True: + # 从 Redis 中获取最后数据库变更时间 + last_db_change_time = cache.get('last_db_change_time', 0) + # 只有当数据库发生变化时才检查总数 + if last_db_change_time and last_db_change_time > last_sent_time: + count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count() + yield f"data: {count}\n\n" + last_sent_time = time.time() + + time.sleep(1) + + +def sse_view(request): + token = request.GET.get('token') + decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256']) + user_id = decoded.get('user_id') + response = StreamingHttpResponse(event_stream(user_id), content_type='text/event-stream') + response['Cache-Control'] = 'no-cache' + return response diff --git a/backend/application/test_settings.py b/backend/application/test_settings.py new file mode 100644 index 0000000..3e0a967 --- /dev/null +++ b/backend/application/test_settings.py @@ -0,0 +1,31 @@ +""" +测试专用 Django Settings +使用已存在的 pisdb 数据库运行测试,测试之间有事务隔离。 +不创建/删除测试库,直接使用 pisdb。 +""" +import os +import sys +from application.settings import * # noqa + +# Patch MSSQL creation class BEFORE any database connections are made +# 这必须在 Django settings 加载之后、任何 DB 操作之前完成 +import mssql.base +from mssql.creation import DatabaseCreation + + +class NoCreateTestDatabase(DatabaseCreation): + """跳过 CREATE/DROP DATABASE 的 MSSQL 创建逻辑""" + + def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): + return self.connection.settings_dict["NAME"] + + def _destroy_test_db(self, test_db_name, verbosity=1, keepdb=False): + pass + + +mssql.base.DatabaseWrapper.creation_class = NoCreateTestDatabase + +# 配置测试数据库 +DATABASES["default"]["TEST"] = { + "NAME": DATABASE_NAME, +} diff --git a/backend/application/urls.py b/backend/application/urls.py new file mode 100644 index 0000000..f8696dc --- /dev/null +++ b/backend/application/urls.py @@ -0,0 +1,134 @@ +"""backend URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.conf.urls.static import static +from django.urls import path, include, re_path +from django.views.static import serve +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from rest_framework import permissions +from rest_framework_simplejwt.views import ( + TokenRefreshView, +) + +from application import dispatch +from application import settings +from application.sse_views import sse_view +from dvadmin.system.views.dictionary import InitDictionaryViewSet +from dvadmin.system.views.login import ( + LoginView, + CaptchaView, + ApiLogin, + LogoutView, + LoginTokenView, + SupplierLoginView, +) +from dvadmin.system.views.system_config import InitSettingsViewSet +from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator + +# =========== 初始化系统配置 ================= +dispatch.init_system_config() +dispatch.init_dictionary() +# =========== 初始化系统配置 ================= + +permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ] +schema_view = get_schema_view( + openapi.Info( + title="Snippets API", + default_version="v1", + description="Test description", + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="contact@snippets.local"), + license=openapi.License(name="BSD License"), + ), + public=True, + permission_classes=permission_classes, + generator_class=CustomOpenAPISchemaGenerator, +) +# 前端页面映射 +from django.http import Http404, HttpResponse +from django.shortcuts import render +import mimetypes +import os + + +def web_view(request): + return render(request, 'web/index.html') + + +def serve_web_files(request, filename): + # 设定文件路径 + filepath = os.path.join(settings.BASE_DIR, 'templates', 'web', filename) + + # 检查文件是否存在 + if not os.path.exists(filepath): + raise Http404("File does not exist") + + # 根据文件扩展名,确定 MIME 类型 + mime_type, _ = mimetypes.guess_type(filepath) + + # 打开文件并读取内容 + with open(filepath, 'rb') as f: + response = HttpResponse(f.read(), content_type=mime_type) + return response + + +urlpatterns = ( + [ + re_path( + r"^swagger(?P\.json|\.yaml)$", + schema_view.without_ui(cache_timeout=0), + name="schema-json", + ), + path( + "", + schema_view.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", + ), + path( + r"redoc/", + schema_view.with_ui("redoc", cache_timeout=0), + name="schema-redoc", + ), + path("api/pisadmin/sync/", include("sync.urls")), + path("api/pisadmin/miscprocurement/", include("apps.pisadmin.miscprocurement.urls")), + path("api/pisadmin/basicinfo/", include("apps.pisadmin.basicinfo.urls")), + path("api/pisadmin/dashboard/", include("apps.pisadmin.dashboard.urls")), + path("api/pissupplier/", include("apps.pissupplier.urls")), + path("api/system/", include("dvadmin.system.urls")), + path("api/login/", LoginView.as_view(), name="token_obtain_pair"), + path("api/login/supplier/", SupplierLoginView.as_view(), name="supplier_login"), + path("api/logout/", LogoutView.as_view(), name="token_obtain_pair"), + path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + re_path( + r"^api-auth/", include("rest_framework.urls", namespace="rest_framework") + ), + path("api/captcha/", CaptchaView.as_view()), + path("api/init/dictionary/", InitDictionaryViewSet.as_view()), + path("api/init/settings/", InitSettingsViewSet.as_view()), + path("apiLogin/", ApiLogin.as_view()), + + # 仅用于开发,上线需关闭 + path("api/token/", LoginTokenView.as_view()), + # 前端页面映射 + path('web/', web_view, name='web_view'), + path('web/', serve_web_files, name='serve_web_files'), + # sse + path('sse/', sse_view, name='sse'), + ] + + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + static(settings.STATIC_URL, document_root=settings.STATIC_URL) + + [re_path(ele.get('re_path'), include(ele.get('include'))) for ele in settings.PLUGINS_URL_PATTERNS] +) diff --git a/backend/application/websocketConfig.py b/backend/application/websocketConfig.py new file mode 100644 index 0000000..6c390f8 --- /dev/null +++ b/backend/application/websocketConfig.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +import urllib + +from asgiref.sync import sync_to_async, async_to_sync +from channels.db import database_sync_to_async +from channels.generic.websocket import AsyncJsonWebsocketConsumer, AsyncWebsocketConsumer +import json + +from channels.layers import get_channel_layer +from jwt import InvalidSignatureError +from rest_framework.request import Request + +from application import settings +from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser +from dvadmin.system.views.message_center import MessageCenterTargetUserSerializer +from dvadmin.utils.serializers import CustomModelSerializer + +send_dict = {} + + +# 发送消息结构体 +def set_message(sender, msg_type, msg, unread=0): + text = { + 'sender': sender, + 'contentType': msg_type, + 'content': msg, + 'unread': unread + } + return text + + +# 异步获取消息中心的目标用户 +@database_sync_to_async +def _get_message_center_instance(message_id): + from dvadmin.system.models import MessageCenter + _MessageCenter = MessageCenter.objects.filter(id=message_id).values_list('target_user', flat=True) + if _MessageCenter: + return _MessageCenter + else: + return [] + + +@database_sync_to_async +def _get_message_unread(user_id): + """获取用户的未读消息数量""" + from dvadmin.system.models import MessageCenterTargetUser + count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count() + return count or 0 + + +def request_data(scope): + query_string = scope.get('query_string', b'').decode('utf-8') + qs = urllib.parse.parse_qs(query_string) + return qs + + +class DvadminWebSocket(AsyncJsonWebsocketConsumer): + async def connect(self): + try: + import jwt + self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"] + decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"]) + if decoded_result: + self.user_id = decoded_result.get('user_id') + self.chat_group_name = "user_" + str(self.user_id) + # 收到连接时候处理, + await self.channel_layer.group_add( + self.chat_group_name, + self.channel_name + ) + await self.accept() + # 主动推送消息 + unread_count = await _get_message_unread(self.user_id) + if unread_count == 0: + # 发送连接成功 + await self.send_json(set_message('system', 'SYSTEM', '您已上线')) + else: + await self.send_json( + set_message('system', 'SYSTEM', "请查看您的未读消息~", + unread=unread_count)) + except InvalidSignatureError: + await self.disconnect(None) + + async def disconnect(self, close_code): + # Leave room group + await self.channel_layer.group_discard(self.chat_group_name, self.channel_name) + print("连接关闭") + try: + await self.close(close_code) + except Exception: + pass + + +class MegCenter(DvadminWebSocket): + """ + 消息中心 + """ + + async def receive(self, text_data): + # 接受客户端的信息,你处理的函数 + text_data_json = json.loads(text_data) + message_id = text_data_json.get('message_id', None) + user_list = await _get_message_center_instance(message_id) + for send_user in user_list: + await self.channel_layer.group_send( + "user_" + str(send_user), + {'type': 'push.message', 'json': text_data_json} + ) + + async def push_message(self, event): + """消息发送""" + message = event['json'] + await self.send(text_data=json.dumps(message)) + + +class MessageCreateSerializer(CustomModelSerializer): + """ + 消息中心-新增-序列化器 + """ + class Meta: + model = MessageCenter + fields = "__all__" + read_only_fields = ["id"] + + +def websocket_push(user_id, message): + username = "user_" + str(user_id) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + username, + { + "type": "push.message", + "json": message + } + ) + + +def create_message_push(title: str, content: str, target_type: int = 0, target_user: list = None, target_dept=None, + target_role=None, message: dict = None, request=Request): + if message is None: + message = {"contentType": "INFO", "content": None} + if target_role is None: + target_role = [] + if target_dept is None: + target_dept = [] + data = { + "title": title, + "content": content, + "target_type": target_type, + "target_user": target_user, + "target_dept": target_dept, + "target_role": target_role + } + message_center_instance = MessageCreateSerializer(data=data, request=request) + message_center_instance.is_valid(raise_exception=True) + message_center_instance.save() + users = target_user or [] + if target_type in [1]: # 按角色 + users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True) + if target_type in [2]: # 按部门 + users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True) + if target_type in [3]: # 系统通知 + users = Users.objects.values_list('id', flat=True) + targetuser_data = [] + for user in users: + targetuser_data.append({ + "messagecenter": message_center_instance.instance.id, + "users": user + }) + targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=request) + targetuser_instance.is_valid(raise_exception=True) + targetuser_instance.save() + for user in users: + username = "user_" + str(user) + unread_count = async_to_sync(_get_message_unread)(user) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + username, + { + "type": "push.message", + "json": {**message, 'unread': unread_count} + } + ) \ No newline at end of file diff --git a/backend/application/ws_routing.py b/backend/application/ws_routing.py new file mode 100644 index 0000000..83a9076 --- /dev/null +++ b/backend/application/ws_routing.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from django.urls import path +from application.websocketConfig import MegCenter + +websocket_urlpatterns = [ + path('ws//', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者 +] \ No newline at end of file diff --git a/backend/application/wsgi.py b/backend/application/wsgi.py new file mode 100644 index 0000000..e63dd0d --- /dev/null +++ b/backend/application/wsgi.py @@ -0,0 +1,17 @@ +""" +WSGI config for backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + +application = get_wsgi_application() diff --git a/backend/apps/__init__.py b/backend/apps/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/apps/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/apps/pisadmin/__init__.py b/backend/apps/pisadmin/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/apps/pisadmin/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/apps/pisadmin/basicinfo/__init__.py b/backend/apps/pisadmin/basicinfo/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/apps/pisadmin/basicinfo/admin.py b/backend/apps/pisadmin/basicinfo/admin.py new file mode 100644 index 0000000..7fcd190 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/admin.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from apps.pisadmin.basicinfo.models import ( + Unit, Currency, Supplier, SupplierUser, Company +) + + +@admin.register(Unit) +class UnitAdmin(admin.ModelAdmin): + list_display = ['unitname', 'unitcode', 'status'] + search_fields = ['unitname', 'unitcode'] + + +@admin.register(Currency) +class CurrencyAdmin(admin.ModelAdmin): + list_display = ['currencyname', 'currencycode', 'factory', 'tax', 'status'] + search_fields = ['currencyname', 'currencycode'] + + +@admin.register(Supplier) +class SupplierAdmin(admin.ModelAdmin): + list_display = ['supplier_name', 'supplier_id', 'company_code', 'contact_person', 'status'] + search_fields = ['supplier_name', 'supplier_id', 'company_code'] + + +@admin.register(SupplierUser) +class SupplierUserAdmin(admin.ModelAdmin): + list_display = ['supplier_id', 'supplier_name', 'user_email', 'user_name', 'status'] + search_fields = ['supplier_id', 'supplier_name', 'user_email', 'user_name'] + + +@admin.register(Company) +class CompanyAdmin(admin.ModelAdmin): + list_display = ['company_code', 'company_name', 'company_short_name', 'status'] + search_fields = ['company_code', 'company_name'] + + diff --git a/backend/apps/pisadmin/basicinfo/apps.py b/backend/apps/pisadmin/basicinfo/apps.py new file mode 100644 index 0000000..d4fa1d9 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/apps.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from django.apps import AppConfig + + +class BasicinfoConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.pisadmin.basicinfo' + verbose_name = "基础信息" diff --git a/backend/apps/pisadmin/basicinfo/factories.py b/backend/apps/pisadmin/basicinfo/factories.py new file mode 100644 index 0000000..39fb433 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/factories.py @@ -0,0 +1,32 @@ +import factory +import factory.django +from apps.pisadmin.basicinfo.models import Currency, Unit + + +class CurrencyFactory(factory.django.DjangoModelFactory): + """ + Currency 模型测试数据工厂 + + 注意: 模型字段名为 'factory',与 factory_boy 模块名冲突, + 故使用 model= (非 factory=) 并通过 LazyAttribute 赋值。 + """ + + class Meta: + model = Currency + # 用 model= 而非 factory= 避免 factory 模块名冲突 + + currencyname = factory.Faker("currency_name") + currencycode = factory.Faker("currency_code") + currencysymbol = factory.Faker("currency_symbol") + # 模型字段 factory 有 null=True,故此处直接赋值为 None + tax = factory.Faker("pydecimal", left_digits=2, right_digits=2, positive=True) + status = 1 + + +class UnitFactory(factory.django.DjangoModelFactory): + class Meta: + model = Unit + + unitname = factory.Faker("word") + unitcode = factory.Faker("random_element", elements=["PC", "KG", "BOX", "SET"]) + status = 1 diff --git a/backend/apps/pisadmin/basicinfo/migrations/0001_initial.py b/backend/apps/pisadmin/basicinfo/migrations/0001_initial.py new file mode 100644 index 0000000..90a2459 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0001_initial.py @@ -0,0 +1,116 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Company', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(help_text='公司代码', max_length=20, verbose_name='公司代码')), + ('company_name', models.CharField(blank=True, help_text='公司全称', max_length=100, null=True, verbose_name='公司全称')), + ('company_short_name', models.CharField(blank=True, help_text='公司简称', max_length=50, null=True, verbose_name='公司简称')), + ('company_address', models.CharField(blank=True, help_text='公司地址', max_length=500, null=True, verbose_name='公司地址')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ('createuser', models.CharField(blank=True, help_text='创建人员', max_length=20, null=True, verbose_name='创建人员')), + ('updateuser', models.CharField(blank=True, help_text='更新人员', max_length=20, null=True, verbose_name='更新人员')), + ], + options={ + 'verbose_name': '公司信息', + 'verbose_name_plural': '公司信息', + 'db_table': 'pis_company', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Currency', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('currencyname', models.CharField(help_text='货币名称', max_length=50, verbose_name='货币名称')), + ('currencycode', models.CharField(blank=True, help_text='货币代码', max_length=20, null=True, verbose_name='货币代码')), + ('currencysymbol', models.CharField(help_text='货币符号', max_length=10, verbose_name='货币符号')), + ('factory', models.CharField(blank=True, help_text='交易厂区(公司)', max_length=10, null=True, verbose_name='交易厂区')), + ('tax', models.DecimalField(decimal_places=2, help_text='税率', max_digits=5, verbose_name='税率')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ], + options={ + 'verbose_name': '币别税率', + 'verbose_name_plural': '币别税率', + 'db_table': 'pis_currencies', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Supplier', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(blank=True, help_text='交易厂区(公司)', max_length=10, null=True, verbose_name='交易厂区')), + ('supplier_id', models.CharField(help_text='供应商唯一ID', max_length=20, unique=True, verbose_name='供应商唯一ID')), + ('supplier_name', models.CharField(help_text='供应商全称', max_length=100, verbose_name='供应商全称')), + ('supplier_short_name', models.CharField(help_text='供应商简称', max_length=50, verbose_name='供应商简称')), + ('vendor_type', models.CharField(blank=True, help_text='厂商性质', max_length=20, null=True, verbose_name='厂商性质')), + ('payment_terms', models.CharField(blank=True, help_text='付款条件', max_length=20, null=True, verbose_name='付款条件')), + ('transaction_currency', models.CharField(blank=True, help_text='交易货币', max_length=20, null=True, verbose_name='交易货币')), + ('incoterms', models.CharField(blank=True, help_text='国际条款', max_length=20, null=True, verbose_name='国际条款')), + ('supplier_level', models.CharField(blank=True, help_text='供应商等级', max_length=10, null=True, verbose_name='供应商等级')), + ('contact_person', models.CharField(help_text='联络人', max_length=20, verbose_name='联络人')), + ('contact_phone', models.CharField(help_text='联络人电话', max_length=20, verbose_name='联络人电话')), + ('contact_email', models.CharField(help_text='联络人邮箱(账号)', max_length=50, verbose_name='联络人邮箱')), + ('country', models.CharField(help_text='国家(如TW,CN)', max_length=20, verbose_name='国家')), + ('province', models.CharField(help_text='省州', max_length=20, verbose_name='省州')), + ('city', models.CharField(blank=True, help_text='城市', max_length=20, null=True, verbose_name='城市')), + ('address', models.CharField(blank=True, help_text='详细地址', max_length=500, null=True, verbose_name='详细地址')), + ('postal_code', models.CharField(blank=True, help_text='邮递区号', max_length=20, null=True, verbose_name='邮递区号')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ], + options={ + 'verbose_name': '供应商信息', + 'verbose_name_plural': '供应商信息', + 'db_table': 'pis_suppliers', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Unit', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('unitname', models.CharField(help_text='计量单位名称', max_length=50, verbose_name='计量单位名称')), + ('unitcode', models.CharField(blank=True, help_text='计量单位代码', max_length=20, null=True, verbose_name='计量单位代码')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ], + options={ + 'verbose_name': '计量单位', + 'verbose_name_plural': '计量单位', + 'db_table': 'pis_units', + 'ordering': ('-create_datetime',), + }, + ), + ] diff --git a/backend/apps/pisadmin/basicinfo/migrations/0002_initial.py b/backend/apps/pisadmin/basicinfo/migrations/0002_initial.py new file mode 100644 index 0000000..4dc91af --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0002_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('basicinfo', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='unit', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='supplier', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='currency', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='company', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + ] diff --git a/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py b/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py new file mode 100644 index 0000000..96e1edc --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0003_systemnorule.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.14 on 2026-03-25 11:40 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('basicinfo', '0002_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SystemNoRule', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(help_text='交易厂区(公司)', max_length=20, verbose_name='交易厂区')), + ('rule_code', models.CharField(choices=[('miscQTS', '杂采报价单'), ('miscRFS', '杂采询价单')], help_text='标识一组单号规则', max_length=50, verbose_name='生成单据标识号')), + ('reset_cycle', models.CharField(choices=[('yy', 'YY'), ('yyyy', 'YYYY'), ('yymm', 'YYMM'), ('yyyymm', 'YYYYMM'), ('yymmdd', 'YYMMDD'), ('yyyymmdd', 'YYYYMMDD')], help_text='重置粒度', max_length=16, verbose_name='流水码重置类别')), + ('prefix', models.CharField(blank=True, help_text='单据开头固定字符', max_length=20, null=True, verbose_name='单据头')), + ('factory_code', models.CharField(blank=True, help_text='厂区区分码', max_length=20, null=True, verbose_name='厂区区分码')), + ('seq_length', models.IntegerField(default=4, help_text='流水码长度', verbose_name='流水码长度')), + ('createuser', models.CharField(blank=True, help_text='单据创建人', max_length=50, null=True, verbose_name='单据创建人')), + ('updateuser', models.CharField(blank=True, help_text='单据修改人', max_length=50, null=True, verbose_name='单据修改人')), + ('sequence_date', models.CharField(blank=True, help_text='与 reset_cycle 对应的日期段(如 yymmdd→260324);新建/改重置类别时由服务端写入', max_length=12, null=True, verbose_name='流水日期')), + ('prev_sequence', models.IntegerField(default=0, help_text='上一笔已成功发放的流水号;未发过为 0,与「下一流水码」相差 1', verbose_name='上一流水码')), + ('current_sequence', models.IntegerField(default=1, help_text='下一笔待发流水号;新建/重置周期后为 1,每次取号成功后 +1', verbose_name='下一流水码')), + ('last_generate_user', models.CharField(blank=True, help_text='最后产生人', max_length=50, null=True, verbose_name='单据最后产生人')), + ('last_generate_time', models.DateTimeField(blank=True, help_text='最后产生时间', null=True, verbose_name='单据最后产生时间')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '系统单号规则', + 'verbose_name_plural': '系统单号规则', + 'db_table': 'pis_system_no_rules', + 'ordering': ('-create_datetime',), + 'unique_together': {('company_code', 'rule_code')}, + }, + ), + ] diff --git a/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py b/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py new file mode 100644 index 0000000..9f4d718 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/0004_emailnotice.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.14 on 2026-03-25 17:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('basicinfo', '0003_systemnorule'), + ] + + operations = [ + migrations.CreateModel( + name='EmailNotice', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('subject', models.CharField(max_length=256, verbose_name='邮件主题')), + ('body', models.TextField(blank=True, null=True, verbose_name='邮件正文')), + ('to_emails', models.JSONField(default=list, verbose_name='收件人')), + ('cc_emails', models.JSONField(default=list, verbose_name='抄送')), + ('bcc_emails', models.JSONField(default=list, verbose_name='密送')), + ('attachments', models.JSONField(default=list, verbose_name='附件')), + ('biz_type', models.CharField(blank=True, max_length=64, null=True, verbose_name='业务类型')), + ('biz_id', models.CharField(blank=True, max_length=128, null=True, verbose_name='业务标识')), + ('status', models.CharField(choices=[('pending', 'pending'), ('sending', 'sending'), ('success', 'success'), ('failed', 'failed')], db_index=True, default='pending', max_length=16, verbose_name='发送状态')), + ('sent_at', models.DateTimeField(blank=True, null=True, verbose_name='发送时间')), + ('message_id', models.CharField(blank=True, max_length=128, null=True, verbose_name='消息ID')), + ('last_error', models.TextField(blank=True, null=True, verbose_name='失败原因')), + ('payload', models.JSONField(default=dict, verbose_name='发送参数')), + ('response', models.JSONField(default=dict, verbose_name='服务端响应')), + ('retry_count', models.IntegerField(default=0, verbose_name='重试次数')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '邮件通知', + 'verbose_name_plural': '邮件通知', + 'db_table': 'pis_proc_email_notice', + 'ordering': ('-update_datetime', '-id'), + }, + ), + ] diff --git a/backend/apps/pisadmin/basicinfo/migrations/__init__.py b/backend/apps/pisadmin/basicinfo/migrations/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/migrations/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/apps/pisadmin/basicinfo/models.py b/backend/apps/pisadmin/basicinfo/models.py new file mode 100644 index 0000000..024889f --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/models.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +from __future__ import annotations + +from django.db import models, transaction +from django.utils import timezone +from rest_framework import serializers +from dvadmin.utils.models import CoreModel, table_prefix + + +class Unit(CoreModel): + """计量单位""" + unitname = models.CharField(max_length=50, verbose_name="计量单位名称", help_text="计量单位名称") + unitcode = models.CharField(max_length=20, verbose_name="计量单位代码", help_text="计量单位代码", null=True, blank=True) + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + + class Meta: + db_table = table_prefix + "units" + verbose_name = "计量单位" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.unitname + + +class Currency(CoreModel): + """货币""" + currencyname = models.CharField(max_length=50, verbose_name="货币名称", help_text="货币名称") + currencycode = models.CharField(max_length=20, verbose_name="货币代码", help_text="货币代码", null=True, blank=True) + currencysymbol = models.CharField(max_length=10, verbose_name="货币符号", help_text="货币符号") + factory = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True) + tax = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="税率", help_text="税率") + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + + class Meta: + db_table = table_prefix + "currencies" + verbose_name = "币别税率" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.currencyname + + +class Supplier(CoreModel): + """供应商信息""" + company_code = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True) + supplier_id = models.CharField(max_length=20, unique=True, verbose_name="供应商唯一ID", help_text="供应商唯一ID") + supplier_name = models.CharField(max_length=100, verbose_name="供应商全称", help_text="供应商全称") + supplier_short_name = models.CharField(max_length=50, verbose_name="供应商简称", help_text="供应商简称") + vendor_type = models.CharField(max_length=20, verbose_name="厂商性质", help_text="厂商性质", null=True, blank=True) + payment_terms = models.CharField(max_length=20, verbose_name="付款条件", help_text="付款条件", null=True, blank=True) + transaction_currency = models.CharField(max_length=20, verbose_name="交易货币", help_text="交易货币", null=True, blank=True) + incoterms = models.CharField(max_length=20, verbose_name="国际条款", help_text="国际条款", null=True, blank=True) + supplier_level = models.CharField(max_length=10, verbose_name="供应商等级", help_text="供应商等级", null=True, blank=True) + contact_person = models.CharField(max_length=20, verbose_name="联络人", help_text="联络人") + contact_phone = models.CharField(max_length=20, verbose_name="联络人电话", help_text="联络人电话") + contact_email = models.CharField(max_length=50, verbose_name="联络人邮箱", help_text="联络人邮箱(账号)") + country = models.CharField(max_length=20, verbose_name="国家", help_text="国家(如TW,CN)") + province = models.CharField(max_length=20, verbose_name="省州", help_text="省州") + city = models.CharField(max_length=20, verbose_name="城市", help_text="城市", null=True, blank=True) + address = models.CharField(max_length=500, verbose_name="详细地址", help_text="详细地址", null=True, blank=True) + postal_code = models.CharField(max_length=20, verbose_name="邮递区号", help_text="邮递区号", null=True, blank=True) + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + + class Meta: + db_table = table_prefix + "suppliers" + verbose_name = "供应商信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.supplier_name + + +class SupplierUser(CoreModel): + """供应商用户信息(独立主数据;通过 supplier_id 与供应商主档逻辑关联,非数据库外键)""" + + ROLE_CHOICES = ( + (1, "supplier_quote"), # 供应商_报价 + ) + + supplier_id = models.CharField( + max_length=20, + db_index=True, + verbose_name="供应商唯一ID", + help_text="与供应商信息表 supplier_id 同值,可多条(多联系人)", + ) + supplier_name = models.CharField(max_length=100, verbose_name="供应商全称", help_text="供应商全称") + supplier_role = models.IntegerField(choices=ROLE_CHOICES, verbose_name="供应商角色", help_text="供应商角色") + user_email = models.CharField(max_length=100, verbose_name="联络人邮箱", help_text="联络人邮箱") + user_name = models.CharField(max_length=20, verbose_name="联络人", help_text="联络人") + user_phone = models.CharField(max_length=100, verbose_name="联络人电话", help_text="联络人电话") + status = models.IntegerField(default=1, verbose_name="有效否", help_text="有效否(1有效0无效)") + + class Meta: + db_table = table_prefix + "supplier_users" + verbose_name = "供应商用户信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + + def __str__(self): + return self.supplier_name + + +class Company(CoreModel): + """公司信息""" + company_code = models.CharField(max_length=20, verbose_name="公司代码", help_text="公司代码") + company_name = models.CharField(max_length=100, verbose_name="公司全称", help_text="公司全称", null=True, blank=True) + company_short_name = models.CharField(max_length=50, verbose_name="公司简称", help_text="公司简称", null=True, blank=True) + company_address = models.CharField(max_length=500, verbose_name="公司地址", help_text="公司地址", null=True, blank=True) + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + createuser = models.CharField(max_length=20, verbose_name="创建人员", help_text="创建人员", null=True, blank=True) + updateuser = models.CharField(max_length=20, verbose_name="更新人员", help_text="更新人员", null=True, blank=True) + + class Meta: + db_table = table_prefix + "company" + verbose_name = "公司信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.company_name or self.company_code + + +class SystemNoRule(CoreModel): + """单据编号规则;取号请用类方法 `allocate_system_number` / `allocate_system_numbers`。""" + + # 与前端「通用」厂区、规则维护一致 + DEFAULT_SYSTEM_NO_COMPANY_CODE = "GENERAL" + + RESET_CYCLE_CHOICES = ( + ("yy", "YY"), + ("yyyy", "YYYY"), + ("yymm", "YYMM"), + ("yyyymm", "YYYYMM"), + ("yymmdd", "YYMMDD"), + ("yyyymmdd", "YYYYMMDD"), + ) + + RESET_CYCLE_STRFTIME = { + "yy": "%y", + "yyyy": "%Y", + "yymm": "%y%m", + "yyyymm": "%Y%m", + "yymmdd": "%y%m%d", + "yyyymmdd": "%Y%m%d", + } + + RULE_CODE_CHOICES = ( + ("miscQTS", "杂采报价单"), + ("miscRFS", "杂采询价单"), + ) + + company_code = models.CharField(max_length=20, verbose_name="交易厂区", help_text="交易厂区(公司)") + rule_code = models.CharField(max_length=50, choices=RULE_CODE_CHOICES, verbose_name="生成单据标识号", help_text="标识一组单号规则") + reset_cycle = models.CharField(max_length=16, choices=RESET_CYCLE_CHOICES, verbose_name="流水码重置类别", help_text="重置粒度") + prefix = models.CharField(max_length=20, verbose_name="单据头", help_text="单据开头固定字符", null=True, blank=True) + factory_code = models.CharField(max_length=20, verbose_name="厂区区分码", help_text="厂区区分码", null=True, blank=True) + seq_length = models.IntegerField(verbose_name="流水码长度", help_text="流水码长度", default=4) + createuser = models.CharField(max_length=50, verbose_name="单据创建人", help_text="单据创建人", null=True, blank=True) + updateuser = models.CharField(max_length=50, verbose_name="单据修改人", help_text="单据修改人", null=True, blank=True) + sequence_date = models.CharField( + max_length=12, + verbose_name="流水日期", + help_text="与 reset_cycle 对应的日期段(如 yymmdd→260324);新建/改重置类别时由服务端写入", + null=True, + blank=True, + ) + prev_sequence = models.IntegerField( + verbose_name="上一流水码", + help_text="上一笔已成功发放的流水号;未发过为 0,与「下一流水码」相差 1", + default=0, + ) + current_sequence = models.IntegerField( + verbose_name="下一流水码", + help_text="下一笔待发流水号;新建/重置周期后为 1,每次取号成功后 +1", + default=1, + ) + last_generate_user = models.CharField(max_length=50, verbose_name="单据最后产生人", help_text="最后产生人", null=True, blank=True) + last_generate_time = models.DateTimeField(verbose_name="单据最后产生时间", help_text="最后产生时间", null=True, blank=True) + + @classmethod + def format_sequence_date(cls, reset_cycle: str, when=None) -> str: + fmt = cls.RESET_CYCLE_STRFTIME.get(reset_cycle or "") + if not fmt: + return "" + dt = when if when is not None else timezone.now() + return dt.strftime(fmt) + + @classmethod + def sequence_period_matches(cls, rule: SystemNoRule, seq_date: str) -> bool: + stored = (rule.sequence_date or "").strip() + return bool(stored) and stored == seq_date + + @classmethod + def _allocate_next_code_for_locked_rule(cls, rule: SystemNoRule, now) -> str: + """假定 rule 已由 select_for_update 锁定;计算下一单号并更新 rule 内存字段(未 save)。""" + seq_date = cls.format_sequence_date(rule.reset_cycle, now) + if not seq_date: + raise serializers.ValidationError("未支持的流水码重置类别") + + if not cls.sequence_period_matches(rule, seq_date): + rule.prev_sequence = 0 + new_seq = 1 + else: + nxt = int(rule.current_sequence or 0) + new_seq = nxt if nxt > 0 else 1 + if ( + rule.last_generate_time is not None + and int(rule.prev_sequence or 0) == 0 + and new_seq == 1 + ): + new_seq = 2 + + slen = max(int(rule.seq_length or 4), 1) + max_seq = 10**slen - 1 + if new_seq > max_seq: + raise serializers.ValidationError( + f"流水码已达上限(长度 {slen} 位,最大 {max_seq}),请调整规则或联系管理员" + ) + + seq_str = str(new_seq).zfill(slen) + code = "".join( + [ + rule.prefix or "", + rule.factory_code or "", + seq_date, + seq_str, + ] + ) + + rule.sequence_date = seq_date + rule.prev_sequence = new_seq + rule.current_sequence = new_seq + 1 + return code + + @classmethod + def allocate_system_number( + cls, + company_code: str, + rule_code: str, + *, + username: str | None = None, + now=None, + ) -> str: + """ + 在事务内对匹配 (company_code, rule_code) 的规则行加锁,生成单号并更新流水字段。 + :param company_code: 交易厂区(与规则表一致,常用 DEFAULT_SYSTEM_NO_COMPANY_CODE) + :param rule_code: 如 miscRFS / miscQTS + """ + codes = cls.allocate_system_numbers(company_code, rule_code, 1, username=username, now=now) + return codes[0] + + @classmethod + def allocate_system_numbers( + cls, + company_code: str, + rule_code: str, + count: int, + *, + username: str | None = None, + now=None, + ) -> list[str]: + """同一事务内连续取 count 个号(一次锁表),用于批量生成报价单等场景。""" + if count <= 0: + return [] + + if not (company_code or "").strip() or not (rule_code or "").strip(): + raise serializers.ValidationError("取号参数缺失:company_code 与 rule_code 必填") + + company_code = str(company_code).strip() + rule_code = str(rule_code).strip() + now = now or timezone.now() + + with transaction.atomic(): + rule = ( + cls.objects.select_for_update() + .filter(company_code=company_code, rule_code=rule_code) + .first() + ) + if not rule: + raise serializers.ValidationError( + f"未配置取号规则:rule_code={rule_code},company_code={company_code},请在「单据编号规则」中维护。" + ) + + codes = [] + for _ in range(count): + codes.append(cls._allocate_next_code_for_locked_rule(rule, now)) + + rule.last_generate_user = username + rule.last_generate_time = now + rule.save( + update_fields=[ + "sequence_date", + "prev_sequence", + "current_sequence", + "last_generate_user", + "last_generate_time", + "update_datetime", + ] + ) + + return codes + + class Meta: + db_table = table_prefix + "system_no_rules" + verbose_name = "系统单号规则" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + unique_together = ("company_code", "rule_code") + + def __str__(self): + return f"{self.rule_code}@{self.company_code}" + + +class EmailNotice(CoreModel): + STATUS_CHOICES = ( + ("pending", "pending"), + ("sending", "sending"), + ("success", "success"), + ("failed", "failed"), + ) + + subject = models.CharField(max_length=256, verbose_name="邮件主题") + body = models.TextField(null=True, blank=True, verbose_name="邮件正文") + to_emails = models.JSONField(default=list, verbose_name="收件人") + cc_emails = models.JSONField(default=list, verbose_name="抄送") + bcc_emails = models.JSONField(default=list, verbose_name="密送") + attachments = models.JSONField(default=list, verbose_name="附件") + biz_type = models.CharField(max_length=64, null=True, blank=True, verbose_name="业务类型") + biz_id = models.CharField(max_length=128, null=True, blank=True, verbose_name="业务标识") + status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="pending", db_index=True, verbose_name="发送状态") + sent_at = models.DateTimeField(null=True, blank=True, verbose_name="发送时间") + message_id = models.CharField(max_length=128, null=True, blank=True, verbose_name="消息ID") + last_error = models.TextField(null=True, blank=True, verbose_name="失败原因") + payload = models.JSONField(default=dict, verbose_name="发送参数") + response = models.JSONField(default=dict, verbose_name="服务端响应") + retry_count = models.IntegerField(default=0, verbose_name="重试次数") + + class Meta: + db_table = table_prefix + "proc_email_notice" + verbose_name = "邮件通知" + verbose_name_plural = verbose_name + ordering = ("-update_datetime", "-id") + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.subject diff --git a/backend/apps/pisadmin/basicinfo/tests/__init__.py b/backend/apps/pisadmin/basicinfo/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/pisadmin/basicinfo/tests/test_serializers.py b/backend/apps/pisadmin/basicinfo/tests/test_serializers.py new file mode 100644 index 0000000..0f650ed --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/tests/test_serializers.py @@ -0,0 +1,118 @@ +import pytest +from apps.pisadmin.basicinfo.factories import CurrencyFactory +from apps.pisadmin.basicinfo.views.currency import CurrencySerializer, CurrencyCreateUpdateSerializer + + +# DRF 的 django_restql DynamicFieldsMixin 使用 model verbose_name 作为 error key +# 例如 currencyname 字段的 error key 是 "货币名称"(模型的 verbose_name) +# 以下是字段名到 error key 的映射 +FIELD_ERROR_KEYS = { + "currencyname": "货币名称", + "currencycode": "货币代码", + "currencysymbol": "货币符号", + "tax": "税率", +} + + +@pytest.mark.django_db +class TestCurrencyCreateUpdateSerializer: + + def test_create_currency_success(self): + """合法数据应创建成功""" + data = { + "currencyname": "人民币", + "currencycode": "CNY", + "currencysymbol": "¥", + "tax": "6.5", + } + serializer = CurrencyCreateUpdateSerializer(data=data) + assert serializer.is_valid(), serializer.errors + instance = serializer.save() + assert instance.currencyname == "人民币" + assert instance.currencycode == "CNY" + + def test_currencyname_required(self): + """currencyname 必填""" + data = { + "currencycode": "CNY", + "currencysymbol": "¥", + "tax": "6.5", + } + serializer = CurrencyCreateUpdateSerializer(data=data) + assert not serializer.is_valid() + assert FIELD_ERROR_KEYS["currencyname"] in serializer.errors + + def test_currencysymbol_required(self): + """currencysymbol 必填""" + data = { + "currencyname": "美元", + "currencycode": "USD", + "tax": "6.5", + } + serializer = CurrencyCreateUpdateSerializer(data=data) + assert not serializer.is_valid() + assert FIELD_ERROR_KEYS["currencysymbol"] in serializer.errors + + def test_tax_required(self): + """tax 必填""" + data = { + "currencyname": "日元", + "currencycode": "JPY", + "currencysymbol": "¥", + } + serializer = CurrencyCreateUpdateSerializer(data=data) + assert not serializer.is_valid() + assert FIELD_ERROR_KEYS["tax"] in serializer.errors + + @pytest.mark.parametrize( + "field,invalid_value", + [ + ("currencyname", ""), # 空字符串 + ("currencyname", "A" * 100), # 超过 max_length=50 + ("currencycode", "A" * 100), # 超过 max_length=20 + ("currencysymbol", "A" * 100), # 超过 max_length=10 + ], + ) + def test_field_max_length_validation(self, field, invalid_value): + """字段长度超限应返回 ValidationError""" + data = { + "currencyname": "测试", + "currencycode": "CNY", + "currencysymbol": "¥", + "tax": "6.5", + } + data[field] = invalid_value + serializer = CurrencyCreateUpdateSerializer(data=data) + assert not serializer.is_valid() + assert FIELD_ERROR_KEYS[field] in serializer.errors + + def test_update_currency_success(self, db): + """更新已有记录""" + existing = CurrencyFactory(currencyname="旧名称") + data = { + "currencyname": "新名称", + "currencycode": "USD", + "currencysymbol": "$", + "tax": "5.0", + } + serializer = CurrencyCreateUpdateSerializer(instance=existing, data=data) + assert serializer.is_valid(), serializer.errors + updated = serializer.save() + assert updated.currencyname == "新名称" + + +@pytest.mark.django_db +class TestCurrencySerializer: + + def test_serialize_currency(self): + """序列化输出包含所有字段""" + currency = CurrencyFactory( + currencyname="欧元", + currencycode="EUR", + currencysymbol="€", + ) + serializer = CurrencySerializer(currency) + data = serializer.data + assert data["currencyname"] == "欧元" + assert data["currencycode"] == "EUR" + assert data["currencysymbol"] == "€" diff --git a/backend/apps/pisadmin/basicinfo/tests/test_views.py b/backend/apps/pisadmin/basicinfo/tests/test_views.py new file mode 100644 index 0000000..af14447 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/tests/test_views.py @@ -0,0 +1,99 @@ +import pytest +from apps.pisadmin.basicinfo.factories import CurrencyFactory +from apps.pisadmin.basicinfo.models import Currency + + +# API 统一响应格式: { "code": 2000, "data": ..., "msg": ... } +# code=2000 表示成功, code=4000 表示认证/权限/参数错误 +CODE_SUCCESS = 2000 +CODE_ERROR = 4000 + + +@pytest.mark.django_db +class TestCurrencyViewSet: + + def test_list_currencies(self, authenticate): + """GET 列表返回成功 + 分页结构""" + CurrencyFactory.create_batch(3) + response = authenticate.get("/api/pisadmin/basicinfo/currencies/") + assert response.data["code"] == CODE_SUCCESS + + def test_create_currency_success(self, authenticate): + """POST 创建返回成功 + DB 记录""" + payload = { + "currencyname": "英镑", + "currencycode": "GBP", + "currencysymbol": "£", + "tax": "20.0", + } + response = authenticate.post( + "/api/pisadmin/basicinfo/currencies/", data=payload, format="json" + ) + assert response.data["code"] == CODE_SUCCESS + assert Currency.objects.filter(currencycode="GBP").exists() + + def test_create_currency_unauthenticated(self, api_client): + """未认证请求返回错误码""" + payload = { + "currencyname": "英镑", + "currencycode": "GBP", + "currencysymbol": "£", + "tax": "20.0", + } + response = api_client.post( + "/api/pisadmin/basicinfo/currencies/", data=payload, format="json" + ) + assert response.data["code"] == CODE_ERROR + + def test_retrieve_currency(self, authenticate): + """GET 详情返回成功 + 完整字段""" + currency = CurrencyFactory(currencyname="澳元", currencycode="AUD") + response = authenticate.get( + f"/api/pisadmin/basicinfo/currencies/{currency.id}/" + ) + assert response.data["code"] == CODE_SUCCESS + assert response.data["data"]["currencyname"] == "澳元" + + def test_update_currency_put(self, authenticate): + """PUT 全量更新返回成功""" + currency = CurrencyFactory(currencyname="旧货币") + payload = { + "currencyname": "新货币", + "currencycode": "NEW", + "currencysymbol": "N", + "tax": "10.0", + } + response = authenticate.put( + f"/api/pisadmin/basicinfo/currencies/{currency.id}/", + data=payload, + format="json", + ) + assert response.data["code"] == CODE_SUCCESS + currency.refresh_from_db() + assert currency.currencyname == "新货币" + + def test_delete_currency(self, authenticate): + """DELETE 返回成功 + 记录消失""" + currency = CurrencyFactory() + response = authenticate.delete( + f"/api/pisadmin/basicinfo/currencies/{currency.id}/" + ) + assert response.data["code"] == CODE_SUCCESS + assert not Currency.objects.filter(id=currency.id).exists() + + def test_delete_currency_unauthenticated_forbidden(self, api_client): + """未认证删除返回错误码""" + currency = CurrencyFactory() + response = api_client.delete( + f"/api/pisadmin/basicinfo/currencies/{currency.id}/" + ) + assert response.data["code"] == CODE_ERROR + + def test_create_currency_invalid_data(self, authenticate): + """非法数据返回错误码 + 错误信息""" + payload = {"currencyname": ""} # 缺少必填字段 + response = authenticate.post( + "/api/pisadmin/basicinfo/currencies/", data=payload, format="json" + ) + assert response.data["code"] == CODE_ERROR + assert "货币名称" in response.data["msg"] diff --git a/backend/apps/pisadmin/basicinfo/urls.py b/backend/apps/pisadmin/basicinfo/urls.py new file mode 100644 index 0000000..d1f2dab --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/urls.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from rest_framework import routers + +from apps.pisadmin.basicinfo.views.unit import UnitViewSet +from apps.pisadmin.basicinfo.views.currency import CurrencyViewSet +from apps.pisadmin.basicinfo.views.supplier import SupplierViewSet, SupplierUserViewSet +from apps.pisadmin.basicinfo.views.company import CompanyViewSet +from apps.pisadmin.basicinfo.views.system_no_rule import SystemNoRuleViewSet +from apps.pisadmin.basicinfo.views.email_utils import EmailNoticeViewSet + +basicinfo_url = routers.SimpleRouter() +basicinfo_url.register(r'units', UnitViewSet) +basicinfo_url.register(r'currencies', CurrencyViewSet) +basicinfo_url.register(r'suppliers', SupplierViewSet) +basicinfo_url.register(r'supplier_users', SupplierUserViewSet) +basicinfo_url.register(r'companies', CompanyViewSet) +basicinfo_url.register(r'system_no_rules', SystemNoRuleViewSet) +basicinfo_url.register(r'email_notice', EmailNoticeViewSet) + +urlpatterns = [] + basicinfo_url.urls diff --git a/backend/apps/pisadmin/basicinfo/views/__init__.py b/backend/apps/pisadmin/basicinfo/views/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/apps/pisadmin/basicinfo/views/company.py b/backend/apps/pisadmin/basicinfo/views/company.py new file mode 100644 index 0000000..b744e57 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/company.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +from rest_framework import serializers + +from apps.pisadmin.basicinfo.models import Company +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class CompanySerializer(CustomModelSerializer): + """公司信息-序列化器""" + + class Meta: + model = Company + fields = "__all__" + read_only_fields = ["id"] + + +class CompanyCreateUpdateSerializer(CustomModelSerializer): + """公司信息 创建/更新""" + + company_code = serializers.CharField(max_length=20) + company_name = serializers.CharField(max_length=100, allow_blank=True, required=False) + company_short_name = serializers.CharField(max_length=50, allow_blank=True, required=False) + company_address = serializers.CharField(max_length=500, allow_blank=True, required=False) + createuser = serializers.CharField(max_length=20, allow_blank=True, required=False) + updateuser = serializers.CharField(max_length=20, allow_blank=True, required=False) + + class Meta: + model = Company + fields = "__all__" + + +class CompanyViewSet(CustomModelViewSet): + """公司信息管理接口""" + + queryset = Company.objects.all() + serializer_class = CompanySerializer + create_serializer_class = CompanyCreateUpdateSerializer + update_serializer_class = CompanyCreateUpdateSerializer + search_fields = ["company_code", "company_name", "company_short_name"] + ordering = ["-create_datetime"] + + def perform_create(self, serializer): + # 自动记录创建人 + username = getattr(getattr(self.request, "user", None), "username", None) + serializer.save(createuser=username) + + def perform_update(self, serializer): + # 自动记录更新人 + username = getattr(getattr(self.request, "user", None), "username", None) + serializer.save(updateuser=username) diff --git a/backend/apps/pisadmin/basicinfo/views/currency.py b/backend/apps/pisadmin/basicinfo/views/currency.py new file mode 100644 index 0000000..e93f93c --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/currency.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from rest_framework import serializers + +from apps.pisadmin.basicinfo.models import Currency +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class CurrencySerializer(CustomModelSerializer): + """币别税率-序列化器""" + + class Meta: + model = Currency + fields = "__all__" + read_only_fields = ["id"] + + +class CurrencyCreateUpdateSerializer(CustomModelSerializer): + """币别税率 创建/更新""" + + currencyname = serializers.CharField(max_length=50) + currencycode = serializers.CharField(max_length=20, allow_blank=True, required=False) + currencysymbol = serializers.CharField(max_length=10) + factory = serializers.CharField(max_length=10, allow_blank=True, required=False) + + class Meta: + model = Currency + fields = "__all__" + + +class CurrencyViewSet(CustomModelViewSet): + """币别税率管理接口""" + + queryset = Currency.objects.all() + serializer_class = CurrencySerializer + create_serializer_class = CurrencyCreateUpdateSerializer + update_serializer_class = CurrencyCreateUpdateSerializer + search_fields = ["currencyname", "currencycode", "currencysymbol", "factory"] + ordering = ["-create_datetime"] diff --git a/backend/apps/pisadmin/basicinfo/views/email_template.py b/backend/apps/pisadmin/basicinfo/views/email_template.py new file mode 100644 index 0000000..3b429b9 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/email_template.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- +"""邮件正文/主题模板:按业务场景 key 渲染,供询价发布、报价结束、询价截止提醒等流程调用。 + +扩展方式: +1. 推荐:单文件 ``emails/_template.html``,首行 ```` 为主题, + 余下为 HTML 正文;在 ``_CUSTOM_RENDERERS`` 中注册渲染函数(通常调用 ``_split_combined_subject_html``)。 +2. 或:在 ``EMAIL_TEMPLATE_FILES`` 登记 (subject.txt, body.html) 路径对,由 ``render_template_pair`` 渲染。 +3. 运行时注册:``register_email_template(..., renderer=...)`` 或 ``register_email_template(..., sub, body)``。 +""" + +from __future__ import annotations + +import os +import re +from typing import Any, Callable, Dict, Optional, Tuple + +from django.conf import settings +from django.contrib.auth import get_user_model +from django.template.loader import render_to_string +from django.utils import timezone + +# 杂采询价单发布通知(新询价邀请) +TEMPLATE_RFS_PUBLISH = "RFS_publish" +# 招标方式发布通知(单文件 HTML:首行注释解析主题 + 正文) +TEMPLATE_BIDS_PUBLISH = "Bids_publish" +# 全部供应商已报价 → 询价单进入「报价结束」— 通知采购负责人 +TEMPLATE_QUOTE_ENDED = "Quote_ended" +# 报价截止后无待报价/报价中单 → 询价单收口为「报价结束」(典型:sync_expired)— 询价截止提醒 +TEMPLATE_QUOTE_TIMEOUT = "Quote_timeout" +# 报价截止时间到期提醒(多个供应商合并一封邮件,按询价单维度) +TEMPLATE_QUOTE_DEADLINE_EXPIRED = "Quote_deadline_expired" + + +def _for_local_display(dt: Any): + """ + 转为用于邮件展示的「本地」时间。 + + - USE_TZ=False 时 ORM 常返回 naive(即便库中是 timestamptz),应直接按业务时区墙钟时间格式化, + 不可调用 timezone.localtime(naive),否则会触发 ValueError。 + - aware 时再用 localtime 转到当前激活时区展示。 + """ + if dt is None: + return None + if timezone.is_naive(dt): + return dt + return timezone.localtime(dt) + + +def supplier_portal_base_url() -> str: + """供应商端入口基址;优先 Django settings,其次环境变量 PIS_SUPPLIER_PORTAL_URL。""" + base = getattr(settings, "PIS_SUPPLIER_PORTAL_URL", None) or os.getenv("PIS_SUPPLIER_PORTAL_URL", "") or "" + return str(base).strip().rstrip("/") + + +def admin_portal_base_url() -> str: + """采购/管理端前端基址;优先 settings.PIS_ADMIN_PORTAL_URL,其次环境变量。""" + base = getattr(settings, "PIS_ADMIN_PORTAL_URL", None) or os.getenv("PIS_ADMIN_PORTAL_URL", "") or "" + return str(base).strip().rstrip("/") + + +def system_brand_name() -> str: + """邮件落款系统名称。""" + return str(getattr(settings, "PIS_EMAIL_SYSTEM_NAME", None) or "AVC PIS").strip() or "AVC PIS" + + +def build_context_rfs_publish( + inquiry: Any, + supplier_group: Dict[str, Any], + *, + purchaser_company_name: str = "", +) -> Dict[str, Any]: + """ + 组装 RFS_publish 模板变量(均为未转义纯文本,由 Django 模板引擎在渲染 HTML 时自动转义)。 + supplier_group:与 `_group_inquiry_suppliers` 一致的结构。 + """ + vendor_name = (supplier_group.get("supplier_name") or "").strip() or "贵司" + rfq_number = (getattr(inquiry, "inquiry_no", None) or "").strip() + title = (getattr(inquiry, "title", None) or "").strip() + + raw_part_ids = supplier_group.get("part_ids") or set() + part_ids = {str(x) for x in raw_part_ids} + lines: list[str] = [] + rfq_mgr = getattr(inquiry, "rfq_items", None) + if rfq_mgr is not None: + for row in rfq_mgr.all(): + if str(row.part_id) not in part_ids: + continue + seg = " ".join(x for x in (row.part_id, (row.product_name or "").strip()) if x).strip() + if seg: + lines.append(seg) + if not lines and part_ids: + lines = sorted(part_ids) + material_info = ";".join(lines) if lines else (title or "—") + + qd = getattr(inquiry, "quote_deadline", None) + # 招标:主表不存报价截止时,发布邮件展示投标截止时间 + if qd is None and int(getattr(inquiry, "buying_method", 1) or 1) == 2: + qd = getattr(inquiry, "bid_end_time", None) + if qd: + local_qd = _for_local_display(qd) + deadline_time = local_qd.strftime("%Y-%m-%d %H:%M") + deadline_date_subject = local_qd.strftime("%Y-%m-%d") + else: + deadline_time = "请登录系统查看" + deadline_date_subject = "待定" + + now = _for_local_display(timezone.now()) + current_date = now.strftime("%Y-%m-%d") if now else "" + + base = supplier_portal_base_url() + system_link = base if base else "" + + buyer = (getattr(inquiry, "buyer", None) or "").strip() + contact_person = buyer or "采购部" + contact_phone = "" # 询价单主表暂无采购电话字段,预留 + + pcn = (purchaser_company_name or "").strip() + if not pcn: + pcn = (getattr(inquiry, "company_code", None) or "").strip() or "我司" + + return { + "vendor_name": vendor_name, + "purchaser_company_name": pcn, + "rfq_number": rfq_number, + "material_info": material_info, + "inquiry_title": title, + "deadline_time": deadline_time, + "deadline_date_subject": deadline_date_subject, + "system_link": system_link, + "contact_person": contact_person, + "contact_phone": contact_phone, + "current_date": current_date, + } + + +def build_context_bids_publish( + inquiry: Any, + supplier_group: Dict[str, Any], + *, + purchaser_company_name: str = "", +) -> Dict[str, Any]: + """ + 招标邀请邮件(``bids_publish_template.html``):在 ``build_context_rfs_publish`` 基础上增加 + ``bid_time_range``、``material_short``、``portal_url``、``purchaser_name`` 等变量。 + """ + ctx = build_context_rfs_publish(inquiry, supplier_group, purchaser_company_name=purchaser_company_name) + + bs = getattr(inquiry, "bid_start_time", None) + be = getattr(inquiry, "bid_end_time", None) + if bs and be: + bsl = _for_local_display(bs) + bel = _for_local_display(be) + if bsl and bel and bsl.date() == bel.date(): + bid_time_range = f"{bsl.strftime('%Y-%m-%d %H:%M')}~{bel.strftime('%H:%M')}" + elif bsl and bel: + bid_time_range = f"{bsl.strftime('%Y-%m-%d %H:%M')} ~ {bel.strftime('%Y-%m-%d %H:%M')}" + else: + bid_time_range = "—" + elif be: + bel = _for_local_display(be) + bid_time_range = bel.strftime("%Y-%m-%d %H:%M") if bel else "请登录系统查看" + elif bs: + bsl = _for_local_display(bs) + bid_time_range = (bsl.strftime("%Y-%m-%d %H:%M") + " 起") if bsl else "请登录系统查看" + else: + bid_time_range = str(ctx.get("deadline_time") or "请登录系统查看") + + mat_plain = str(ctx.get("material_info") or "") + material_short = mat_plain[:120] + ("…" if len(mat_plain) > 120 else "") + + purchaser_name = _quote_ended_purchaser_name(inquiry) + portal_url = str(ctx.get("system_link") or "").strip() + + ctx.update( + { + "bid_time_range": bid_time_range, + "material_short": material_short, + "portal_url": portal_url, + "purchaser_name": purchaser_name, + "purchaser_phone": str(ctx.get("contact_phone") or "").strip(), + } + ) + return ctx + + +def _quote_ended_purchaser_name(inquiry: Any) -> str: + """采购负责人展示名:优先系统用户姓名,否则采购负责人字段原文。""" + buyer = (getattr(inquiry, "buyer", None) or "").strip() + if not buyer: + return "采购同事" + User = get_user_model() + u = User.objects.filter(username=buyer).exclude(name__isnull=True).exclude(name="").first() + if not u: + u = User.objects.filter(name=buyer).first() + if u and (getattr(u, "name", None) or "").strip(): + return str(u.name).strip() + return buyer + + +def _quote_ended_material_or_project_name(inquiry: Any) -> str: + """物料/项目名称:优先 RFQ 行料号+品名汇总,否则询价单名称/材料类型。""" + lines: list[str] = [] + rfq_mgr = getattr(inquiry, "rfq_items", None) + if rfq_mgr is not None: + for row in rfq_mgr.all(): + seg = " ".join(x for x in (row.part_id, (getattr(row, "product_name", None) or "").strip()) if x).strip() + if seg: + lines.append(seg) + if lines: + return ";".join(lines) + title = (getattr(inquiry, "title", None) or "").strip() + if title: + return title + mt = (getattr(inquiry, "material_type", None) or "").strip() + return mt or "—" + + +def _quote_ended_completion_time(inquiry: Any) -> Any: + """全部已报价单中,取最晚的报价时间(quotetime 优先,否则 creattime)。""" + from apps.pissupplier.models import QuotationMaster + + inq_no = (getattr(inquiry, "inquiry_no", None) or "").strip() + if not inq_no: + return None + latest: Any = None + qs = QuotationMaster.objects.filter(inquiry_no=inq_no, status=3).only("quotetime", "creattime") + for row in qs: + t = row.quotetime or row.creattime + if t is None: + continue + if latest is None or t > latest: + latest = t + return latest + + +def build_context_quote_ended( + inquiry: Any, + *, + last_quotation_no: str = "", +) -> Dict[str, Any]: + """ + 报价完成通知(采购端):受邀供应商均已提交报价,与模板「报价完成通知」文案一致。 + """ + from apps.pisadmin.miscprocurement.models import InquirySupplier + + inquiry_no = (getattr(inquiry, "inquiry_no", None) or "").strip() + title = (getattr(inquiry, "title", None) or "").strip() + inquiry_name = title or "—" + + purchaser_name = _quote_ended_purchaser_name(inquiry) + material_or_project_name = _quote_ended_material_or_project_name(inquiry) + plain = material_or_project_name + material_or_project_short = plain[:40] + ("…" if len(plain) > 40 else "") if plain and plain != "—" else "—" + + supplier_count = ( + InquirySupplier.objects.filter(inquiry_no=inquiry) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + + raw_done = _quote_ended_completion_time(inquiry) + if raw_done is None: + raw_done = _for_local_display(timezone.now()) + done_local = _for_local_display(raw_done) + completion_time = done_local.strftime("%Y-%m-%d %H:%M") if done_local else "—" + + base = admin_portal_base_url() + pk = getattr(inquiry, "pk", None) or getattr(inquiry, "id", None) + comparison_page_url = "" + if base and pk is not None: + comparison_page_url = f"{base}/pisadmin/miscprocurement/rfqmiscellaneous/comparePrice/{pk}" + + now = _for_local_display(timezone.now()) + current_date = now.strftime("%Y-%m-%d") if now else "" + + return { + "purchaser_name": purchaser_name, + "inquiry_no": inquiry_no, + "inquiry_name": inquiry_name, + "material_or_project_name": material_or_project_name, + "material_or_project_short": material_or_project_short, + "supplier_count": supplier_count, + "completion_time": completion_time, + "comparison_page_url": comparison_page_url, + # 兼容旧模板变量(若外部仍有引用) + "rfq_number": inquiry_no, + "inquiry_title": inquiry_name, + "inquiry_title_short": material_or_project_short, + "last_quotation_no": (last_quotation_no or "").strip(), + "closed_time": completion_time, + "current_date": current_date, + "admin_link": comparison_page_url or base, + "system_name": system_brand_name(), + } + + +def _inquiry_effective_deadline_for_display(inquiry: Any) -> Tuple[Any, str]: + """询价/招标下用于邮件展示的截止时间:询价用 quote_deadline,招标用 bid_end_time。""" + qd = getattr(inquiry, "quote_deadline", None) + if qd is None and int(getattr(inquiry, "buying_method", 1) or 1) == 2: + qd = getattr(inquiry, "bid_end_time", None) + if not qd: + return None, "—" + loc = _for_local_display(qd) + return qd, loc.strftime("%Y-%m-%d %H:%M") if loc else "—" + + +def build_context_quote_timeout(inquiry: Any) -> Dict[str, Any]: + """ + 询价截止提醒(采购端):已到截止时间、需查看报价并完成比价;与模板「询价截止提醒」文案一致。 + """ + from apps.pisadmin.miscprocurement.models import InquirySupplier + from apps.pissupplier.models import QuotationMaster + + inquiry_no = (getattr(inquiry, "inquiry_no", None) or "").strip() + title = (getattr(inquiry, "title", None) or "").strip() + inquiry_name = title or "—" + + purchaser_name = _quote_ended_purchaser_name(inquiry) + material_or_project_name = _quote_ended_material_or_project_name(inquiry) + plain = material_or_project_name + material_or_project_short = plain[:40] + ("…" if len(plain) > 40 else "") if plain and plain != "—" else "—" + + _, deadline_display = _inquiry_effective_deadline_for_display(inquiry) + + invited_count = ( + InquirySupplier.objects.filter(inquiry_no=inquiry) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + + quoted_count = ( + QuotationMaster.objects.filter(inquiry_no=inquiry_no, status=3) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + + base = admin_portal_base_url() + pk = getattr(inquiry, "pk", None) or getattr(inquiry, "id", None) + comparison_page_url = "" + if base and pk is not None: + comparison_page_url = f"{base}/pisadmin/miscprocurement/rfqmiscellaneous/comparePrice/{pk}" + + now = _for_local_display(timezone.now()) + current_date = now.strftime("%Y-%m-%d") if now else "" + + return { + "purchaser_name": purchaser_name, + "inquiry_no": inquiry_no, + "inquiry_name": inquiry_name, + "material_or_project_name": material_or_project_name, + "material_or_project_short": material_or_project_short, + "deadline": deadline_display, + "quoted_count": quoted_count, + "invited_count": invited_count, + "comparison_page_url": comparison_page_url, + "rfq_number": inquiry_no, + "inquiry_title": inquiry_name, + "current_date": current_date, + "admin_link": comparison_page_url or base, + "system_name": system_brand_name(), + } + + +def build_context_quote_deadline_expired( + inquiry: Any, + expired_suppliers: list, +) -> Dict[str, Any]: + """ + 报价截止到期提醒(采购端):按询价单合并多个已到期未报价供应商,发送一封邮件。 + expired_suppliers: list[{"supplier_name", "contact_person", "quote_deadline", "status"}, ...] + """ + from apps.pisadmin.miscprocurement.models import InquirySupplier + + inquiry_no = (getattr(inquiry, "inquiry_no", None) or "").strip() + title = (getattr(inquiry, "title", None) or "").strip() + inquiry_name = title or "—" + + purchaser_name = _quote_ended_purchaser_name(inquiry) + material_or_project_name = _quote_ended_material_or_project_name(inquiry) + + invited_count = ( + InquirySupplier.objects.filter(inquiry_no=inquiry) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + + base = admin_portal_base_url() + pk = getattr(inquiry, "pk", None) or getattr(inquiry, "id", None) + comparison_page_url = "" + if base and pk is not None: + comparison_page_url = f"{base}/pisadmin/miscprocurement/rfqmiscellaneous/comparePrice/{pk}" + + return { + "purchaser_name": purchaser_name, + "inquiry_no": inquiry_no, + "inquiry_name": inquiry_name, + "material_or_project_name": material_or_project_name, + "supplier_count": len(expired_suppliers), + "expired_suppliers": expired_suppliers, + "invited_count": invited_count, + "comparison_page_url": comparison_page_url, + "current_date": _for_local_display(timezone.now()).strftime("%Y-%m-%d"), + "admin_link": comparison_page_url or base, + "system_name": system_brand_name(), + } + + +# --------------------------------------------------------------------------- +# 可选:template_key -> (subject 相对 templates/, body 相对 templates/),由 render_template_pair 渲染。 +# 内置场景均已改为单文件 *_template.html,见 _CUSTOM_RENDERERS。 +# --------------------------------------------------------------------------- +EMAIL_TEMPLATE_FILES: Dict[str, Tuple[str, str]] = {} + + +def render_template_pair( + subject_template: str, + body_template: str, + context: Dict[str, Any], +) -> Tuple[str, str]: + """ + 通用:按一对 Django 模板路径渲染 (subject, html_body)。 + subject 一般为纯文本;HTML 正文由引擎对变量自动转义(HTML 安全)。 + """ + subject = render_to_string(subject_template, context).strip() + body = render_to_string(body_template, context) + return subject, body + + +def _render_rfs_publish_body(ctx: Dict[str, Any]) -> Tuple[str, str]: + """RFS_publish:单文件 ``rfs_publish_template.html``;主题需 material_short,在渲染前注入。""" + mat_plain = str(ctx.get("material_info") or "") + material_short = mat_plain[:120] + ("…" if len(mat_plain) > 120 else "") + + render_ctx: Dict[str, Any] = { + **ctx, + "material_short": material_short, + } + html = render_to_string("emails/rfs_publish_template.html", render_ctx) + return _split_combined_subject_html(html) + + +def _render_quote_ended_combined(ctx: Dict[str, Any]) -> Tuple[str, str]: + """Quote_ended:单文件 ``quote_ended_template.html``。""" + html = render_to_string("emails/quote_ended_template.html", ctx) + return _split_combined_subject_html(html) + + +def _render_quote_timeout_combined(ctx: Dict[str, Any]) -> Tuple[str, str]: + """Quote_timeout:单文件 ``quote_timeout_template.html``。""" + html = render_to_string("emails/quote_timeout_template.html", ctx) + return _split_combined_subject_html(html) + + +# 首行必须为:(主题内勿含连续两个减号 ``--``,以免破坏 HTML 注释) +_COMBINED_EMAIL_SUBJECT_RE = re.compile( + r"^\s*\s*", + re.IGNORECASE | re.DOTALL, +) + + +def _split_combined_subject_html(html: str) -> Tuple[str, str]: + """从合并模板中解析 (subject, body_html)。""" + m = _COMBINED_EMAIL_SUBJECT_RE.match(html) + if not m: + raise ValueError( + "合并邮件模板必须以 开头(首行),参见 emails/*_template.html" + ) + subject = m.group(1).replace("\r", " ").replace("\n", " ").strip() + body = html[m.end() :].lstrip() + return subject, body + + +def _render_bids_publish_combined(ctx: Dict[str, Any]) -> Tuple[str, str]: + """Bids_publish:单文件 HTML,首行注释为主题。""" + html = render_to_string("emails/bids_publish_template.html", ctx) + return _split_combined_subject_html(html) + + +_CUSTOM_RENDERERS: Dict[str, Callable[[Dict[str, Any]], Tuple[str, str]]] = { + TEMPLATE_RFS_PUBLISH: _render_rfs_publish_body, + TEMPLATE_BIDS_PUBLISH: _render_bids_publish_combined, + TEMPLATE_QUOTE_ENDED: _render_quote_ended_combined, + TEMPLATE_QUOTE_TIMEOUT: _render_quote_timeout_combined, +} + + +def render_email(template_key: str, context: Dict[str, Any]) -> Tuple[str, str]: + """ + 按模板类型渲染邮件。 + + 解析顺序: + 1. ``_CUSTOM_RENDERERS``(单文件 ``*_template.html`` 等); + 2. ``EMAIL_TEMPLATE_FILES`` 中的 (subject, body) 路径对; + 否则抛出 ``ValueError``。 + + :return: (subject, body);body 为 HTML 时由调用方在 EmailNotice.payload 中标记 is_html。 + """ + custom = _CUSTOM_RENDERERS.get(template_key) + if custom: + return custom(context) + + paths = EMAIL_TEMPLATE_FILES.get(template_key) + if paths: + sub_path, body_path = paths + return render_template_pair(sub_path, body_path, context) + + raise ValueError(f"Unknown email template: {template_key!r}") + + +def register_email_template( + template_key: str, + subject_template: str, + body_template: str, + *, + renderer: Optional[Callable[[Dict[str, Any]], Tuple[str, str]]] = None, +) -> None: + """ + 运行时注册(可选):用于插件或测试注入额外模板。 + + - 若提供 ``renderer``,则走自定义渲染,不再使用路径对。 + - 否则将 ``subject_template`` / ``body_template`` 登记到 ``EMAIL_TEMPLATE_FILES``。 + """ + if renderer is not None: + _CUSTOM_RENDERERS[template_key] = renderer + EMAIL_TEMPLATE_FILES.pop(template_key, None) + return + EMAIL_TEMPLATE_FILES[template_key] = (subject_template, body_template) + _CUSTOM_RENDERERS.pop(template_key, None) diff --git a/backend/apps/pisadmin/basicinfo/views/email_utils.py b/backend/apps/pisadmin/basicinfo/views/email_utils.py new file mode 100644 index 0000000..a5dd0e3 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/email_utils.py @@ -0,0 +1,505 @@ +import logging +import os +import tempfile +import urllib.parse +import urllib.request +from datetime import timedelta +from typing import Any, Dict, List, Tuple +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.response import Response + +from django.conf import settings +from django.core.mail import EmailMessage, get_connection +from django.utils import timezone +from django.utils.dateparse import parse_datetime +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet +from django.contrib.auth import get_user_model + +from apps.pisadmin.basicinfo.models import EmailNotice + +logger = logging.getLogger(__name__) + + +def _collect_attachment_paths(raw_list: List[Any]) -> List[str]: + paths: List[str] = [] + if not isinstance(raw_list, list): + return paths + for item in raw_list: + if isinstance(item, str): + if item: + paths.append(item) + continue + if isinstance(item, dict): + candidate = item.get("file_path") or item.get("path") or item.get("filepath") + url = item.get("url") + if candidate: + paths.append(candidate) + elif url and isinstance(url, str) and url.startswith("http"): + # download remote file to temp location + try: + parsed = urllib.parse.urlparse(url) + filename = os.path.basename(parsed.path) or "attachment" + fd, temp_path = tempfile.mkstemp(prefix="mail_att_", suffix=os.path.splitext(filename)[1]) + os.close(fd) + urllib.request.urlretrieve(url, temp_path) + paths.append(temp_path) + except Exception: + # skip silently; caller can still send email without this attachment + continue + return paths + + +def resolve_inquiry_purchaser_emails(inquiry) -> list: + """ + 根据询价单解析采购端收件人邮箱:优先「采购负责人」对应系统用户邮箱; + 若无则依次尝试创建人(creator / creator_id)、创建人账号(create_user)。 + """ + User = get_user_model() + + buyer = (getattr(inquiry, "buyer", None) or "").strip() + if buyer: + u = User.objects.filter(username=buyer).exclude(email__isnull=True).exclude(email="").first() + if not u: + u = User.objects.filter(name=buyer).exclude(email__isnull=True).exclude(email="").first() + if u and getattr(u, "email", None): + return [str(u.email).strip()] + + cr = getattr(inquiry, "creator", None) + if cr is not None and getattr(cr, "email", None): + return [str(cr.email).strip()] + cid = getattr(inquiry, "creator_id", None) + if cid: + u = User.objects.filter(id=cid).exclude(email__isnull=True).exclude(email="").first() + if u and getattr(u, "email", None): + return [str(u.email).strip()] + + cu = (getattr(inquiry, "create_user", None) or "").strip() + if cu: + u = User.objects.filter(username=cu).exclude(email__isnull=True).exclude(email="").first() + if u and getattr(u, "email", None): + return [str(u.email).strip()] + + return [] + + +def send_quote_ended_notice_to_purchaser(inquiry, *, last_quotation_no: str = "") -> bool: + """ + 询价单进入「报价结束」时通知采购负责人(HTML 邮件 + EmailNotice 记录)。 + 正文与主题由 ``emails/quote_ended_template.html`` 首行 ```` 解析。 + 无有效收件人时跳过发送,返回 False。 + """ + from apps.pisadmin.basicinfo.views.email_template import ( + TEMPLATE_QUOTE_ENDED, + build_context_quote_ended, + render_email, + ) + + to_list = resolve_inquiry_purchaser_emails(inquiry) + if not to_list: + return False + + ctx = build_context_quote_ended(inquiry, last_quotation_no=last_quotation_no or "") + subject, body = render_email(TEMPLATE_QUOTE_ENDED, ctx) + + notice = EmailNotice.objects.create( + subject=subject, + body=body, + to_emails=to_list, + cc_emails=[], + bcc_emails=[], + attachments=[], + biz_type="inquiry_quote_ended", + biz_id=getattr(inquiry, "inquiry_no", None) or "", + status="pending", + payload={ + "template_key": TEMPLATE_QUOTE_ENDED, + "is_html": True, + "inquiry_no": ctx.get("inquiry_no"), + "supplier_count": ctx.get("supplier_count"), + "completion_time": ctx.get("completion_time"), + }, + ) + notice.status = "sending" + notice.save(update_fields=["status", "update_datetime"]) + + success, detail = send_email_notice(notice) + notice.response = detail or {} + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + return success + + +def should_send_quote_timeout_reminder(inquiry_no: str, *, cooldown_hours: int = 24) -> bool: + """ + 同一询价单「询价截止提醒」在冷却期内不重复发送,避免供应商端多次刷新列表/详情导致采购重复收信。 + """ + key = (inquiry_no or "").strip() + if not key: + return False + cutoff = timezone.now() - timedelta(hours=cooldown_hours) + return not EmailNotice.objects.filter( + biz_type="inquiry_quote_timeout", + biz_id=key, + status="success", + sent_at__gte=cutoff, + ).exists() + + +def send_quote_timeout_notice_to_purchaser(inquiry) -> bool: + """ + 询价截止提醒(HTML + EmailNotice):由 ``notify_purchasers_quote_timeout_for_inquiries`` 在 + 供应商端 ``POST .../quotation_master/sync_expired/`` 将超期未报价单置为已过期之后按需调用。 + 正文与主题由 ``emails/quote_timeout_template.html`` 首行 ```` 解析。 + 无有效收件人时跳过发送,返回 False。 + """ + from apps.pisadmin.basicinfo.views.email_template import ( + TEMPLATE_QUOTE_TIMEOUT, + build_context_quote_timeout, + render_email, + ) + + to_list = resolve_inquiry_purchaser_emails(inquiry) + if not to_list: + return False + + ctx = build_context_quote_timeout(inquiry) + subject, body = render_email(TEMPLATE_QUOTE_TIMEOUT, ctx) + + notice = EmailNotice.objects.create( + subject=subject, + body=body, + to_emails=to_list, + cc_emails=[], + bcc_emails=[], + attachments=[], + biz_type="inquiry_quote_timeout", + biz_id=getattr(inquiry, "inquiry_no", None) or "", + status="pending", + payload={ + "template_key": TEMPLATE_QUOTE_TIMEOUT, + "is_html": True, + "inquiry_no": ctx.get("inquiry_no"), + "quoted_count": ctx.get("quoted_count"), + "invited_count": ctx.get("invited_count"), + "deadline": ctx.get("deadline"), + }, + ) + notice.status = "sending" + notice.save(update_fields=["status", "update_datetime"]) + + success, detail = send_email_notice(notice) + notice.response = detail or {} + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + return success + + +def notify_purchasers_quote_timeout_for_inquiries(inquiry_nos: List[str]) -> int: + """ + 在供应商端 ``sync_expired`` 将超期未报价单置为已过期之后调用:对涉及询价单向采购负责人发送截止提醒邮件。 + 返回成功发送的询价单数量(去重后、且受冷却期限制)。 + """ + from apps.pisadmin.miscprocurement.models import Inquiry + + unique = sorted({(x or "").strip() for x in inquiry_nos if x and str(x).strip()}) + sent = 0 + for inq_no in unique: + if not should_send_quote_timeout_reminder(inq_no): + continue + inq = Inquiry.objects.filter(inquiry_no=inq_no).first() + if not inq: + continue + try: + if send_quote_timeout_notice_to_purchaser(inq): + sent += 1 + except Exception: + logger.exception("询价截止提醒邮件发送失败 inquiry_no=%s", inq_no) + return sent + + +def send_bids_publish_notice_to_supplier( + inquiry, + supplier_group: Dict[str, Any], + *, + purchaser_company_name: str = "", +) -> Tuple[bool, EmailNotice]: + """ + 招标邀请邮件(单文件 ``bids_publish_template.html``):向单个供应商分组发送 HTML 邮件并写入 EmailNotice。 + + 无有效收件人时仍创建 notice 并标记失败,返回 (False, notice);成功发送返回 (True, notice)。 + """ + from apps.pisadmin.basicinfo.views.email_template import ( + TEMPLATE_BIDS_PUBLISH, + build_context_bids_publish, + render_email, + ) + + email = (supplier_group.get("contact_email") or "").strip() + to_list = [email] if email else [] + + ctx = build_context_bids_publish( + inquiry, + supplier_group, + purchaser_company_name=purchaser_company_name or "", + ) + subject, body = render_email(TEMPLATE_BIDS_PUBLISH, ctx) + + notice = EmailNotice.objects.create( + subject=subject, + body=body, + to_emails=to_list, + cc_emails=[], + bcc_emails=[], + attachments=[], + biz_type="inquiry_bids_publish", + biz_id=getattr(inquiry, "inquiry_no", None) or "", + status="pending", + payload={ + "template_key": TEMPLATE_BIDS_PUBLISH, + "is_html": True, + "inquiry_no": ctx.get("rfq_number"), + "inquiry_title": ctx.get("inquiry_title"), + "bid_time_range": ctx.get("bid_time_range"), + "supplier_name": (supplier_group.get("supplier_name") or "").strip(), + }, + ) + + if not to_list: + notice.status = "failed" + notice.last_error = "缺少供应商邮箱" + notice.save(update_fields=["status", "last_error", "update_datetime"]) + return False, notice + + notice.status = "sending" + notice.save(update_fields=["status", "update_datetime"]) + + success, detail = send_email_notice(notice) + notice.response = detail or {} + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + return success, notice + + +def send_email_notice(notice) -> Tuple[bool, Dict[str, Any]]: + """Send an email based on EmailNotice instance. + + Returns (success, details) + """ + + # 无认证中继(如内网 25 端口)允许 EMAIL_HOST_USER / EMAIL_HOST_PASSWORD 为空;Django SMTP 仅在两者均有值时 login + host = (getattr(settings, "EMAIL_HOST", None) or "").strip() + port_raw = getattr(settings, "EMAIL_PORT", None) + if not host: + return False, {"error": "邮件配置不完整:未配置 SMTP 主机 EMAIL_HOST"} + try: + port = int(port_raw) + except (TypeError, ValueError): + return False, {"error": "邮件配置不完整:EMAIL_PORT 无效"} + if not (1 <= port <= 65535): + return False, {"error": "邮件配置不完整:EMAIL_PORT 无效"} + + to_list = notice.to_emails or [] + cc_list = notice.cc_emails or [] + bcc_list = notice.bcc_emails or [] + if not to_list: + return False, {"error": "收件人为空"} + + use_ssl = getattr(settings, "EMAIL_USE_SSL", False) + use_tls = getattr(settings, "EMAIL_USE_TLS", False) + from_email = (getattr(settings, "EMAIL_FROM", None) or "").strip() or ( + (getattr(settings, "EMAIL_HOST_USER", None) or "").strip() + ) + if not from_email: + return False, {"error": "邮件配置不完整:请配置 EMAIL_FROM 或 EMAIL_HOST_USER 作为发件人"} + + try: + connection = get_connection( + fail_silently=False, + host=host, + port=port, + use_ssl=use_ssl, + use_tls=use_tls, + timeout=20, + ) + + msg = EmailMessage( + subject=notice.subject or "", + body=notice.body or "", + from_email=from_email, + to=to_list, + cc=cc_list, + bcc=bcc_list, + connection=connection, + ) + + payload = notice.payload or {} + is_html = bool(payload.get("is_html") or payload.get("is_body_html")) + if is_html: + msg.content_subtype = "html" + + temp_files: List[str] = [] + try: + paths = _collect_attachment_paths(notice.attachments) + for path in paths: + if not path: + continue + try: + msg.attach_file(path) + except Exception: + # 如果文件不可用,忽略该附件继续 + continue + if path.startswith(tempfile.gettempdir()): + temp_files.append(path) + msg.send(fail_silently=False) + finally: + for f in temp_files: + try: + os.remove(f) + except Exception: + pass + + return True, {"message": "sent"} + except Exception as exc: + return False, {"error": str(exc)} + + +class EmailNoticeSerializer(CustomModelSerializer): + to_emails = serializers.JSONField(required=False) + cc_emails = serializers.JSONField(required=False) + bcc_emails = serializers.JSONField(required=False) + attachments = serializers.JSONField(required=False) + payload = serializers.JSONField(required=False) + response = serializers.JSONField(required=False) + + class Meta: + model = EmailNotice + fields = "__all__" + read_only_fields = ["id", "create_datetime", "update_datetime", "creator", "modifier"] + + def _ensure_list(self, value, field_name): + if value is None: + return [] + if isinstance(value, str): + try: + import json + + parsed = json.loads(value) + if isinstance(parsed, list): + return parsed + except Exception: + pass + raise serializers.ValidationError(f"{field_name} must be a list") + if not isinstance(value, list): + raise serializers.ValidationError(f"{field_name} must be a list") + return value + + def _ensure_dict(self, value, field_name): + if value is None: + return {} + if isinstance(value, str): + try: + import json + + parsed = json.loads(value) + if isinstance(parsed, dict): + return parsed + except Exception: + pass + raise serializers.ValidationError(f"{field_name} must be a dict") + if not isinstance(value, dict): + raise serializers.ValidationError(f"{field_name} must be a dict") + return value + + def validate_to_emails(self, value): + return self._ensure_list(value, "to_emails") + + def validate_cc_emails(self, value): + return self._ensure_list(value, "cc_emails") + + def validate_bcc_emails(self, value): + return self._ensure_list(value, "bcc_emails") + + def validate_attachments(self, value): + return self._ensure_list(value, "attachments") + + def validate_payload(self, value): + return self._ensure_dict(value, "payload") + + def validate_response(self, value): + return self._ensure_dict(value, "response") + + +class EmailNoticeViewSet(CustomModelViewSet): + queryset = EmailNotice.objects.all() + serializer_class = EmailNoticeSerializer + filter_fields = ("subject", "status", "biz_type", "biz_id") + search_fields = ("subject", "biz_type", "biz_id", "last_error", "message_id") + ordering_fields = ("sent_at", "update_datetime", "create_datetime") + ordering = ("-sent_at", "-update_datetime") + + def get_queryset(self): + qs = super().get_queryset() + params = getattr(self, "request", None) + if not params: + return qs + + raw_range = self.request.query_params.getlist("sent_at") or [] + if not raw_range: + raw_value = self.request.query_params.get("sent_at") + if raw_value: + raw_range = raw_value.split(",") if "," in raw_value else raw_value.split("|") + + if len(raw_range) >= 2: + start = parse_datetime(raw_range[0].strip()) + end = parse_datetime(raw_range[1].strip()) + if start and end: + qs = qs.filter(sent_at__range=(start, end)) + + return qs + + @action(detail=True, methods=["post"], url_path="resend") + def resend(self, request, pk=None): + notice = self.get_object() + + # update retry count and mark sending + notice.retry_count = (notice.retry_count or 0) + 1 + notice.status = "sending" + notice.save(update_fields=["retry_count", "status", "update_datetime"]) + + success, detail = send_email_notice(notice) + + notice.response = detail + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + + return Response({"success": success, "detail": detail}) diff --git a/backend/apps/pisadmin/basicinfo/views/supplier.py b/backend/apps/pisadmin/basicinfo/views/supplier.py new file mode 100644 index 0000000..50c3286 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/supplier.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +import hashlib + +from django.contrib.auth.hashers import make_password +from django.db import IntegrityError, transaction +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from application import dispatch +from apps.pisadmin.basicinfo.models import Supplier, SupplierUser +from dvadmin.system.models import Dept, Role, Users +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + +SUPPLIER_DEPT_KEY = "supplier" + + +def _default_system_password_plain() -> str: + v = dispatch.get_system_config_values("base.default_password") + if v is None or str(v).strip() == "": + return "admin123456" + return str(v).strip() + + +def _system_role_key_from_supplier_role(supplier_role: int) -> str: + role_map = dict(SupplierUser.ROLE_CHOICES) + key = role_map.get(supplier_role) + if not key: + raise ValidationError( + detail={"supplier_role": [f"不支持的供应商角色({supplier_role}),无法映射系统角色"]} + ) + return key + + +def _sync_create_system_user_for_supplier(supplier_user: SupplierUser, request) -> None: + """在系统用户表创建账号:账号=联络人邮箱,部门 key=supplier,角色按 supplier_role 映射,性别=0,密码=系统默认密码。""" + email = (supplier_user.user_email or "").strip() + if not email: + raise ValidationError(detail={"user_email": ["联络人邮箱不能为空(系统登录账号将使用该邮箱)"]}) + + if Users.objects.filter(username=email).exists(): + raise ValidationError( + detail={ + "user_email": ["该邮箱已作为系统用户账号存在,请更换联络人邮箱或联系管理员处理已有账号"] + } + ) + + dept = Dept.objects.filter(key=SUPPLIER_DEPT_KEY, status=True).first() or Dept.objects.filter( + key=SUPPLIER_DEPT_KEY + ).first() + if not dept: + raise ValidationError( + detail={ + "_dept": [f'未找到部门 key="{SUPPLIER_DEPT_KEY}"(供应商),请先在「部门管理」中维护'] + } + ) + + role_key = _system_role_key_from_supplier_role(supplier_user.supplier_role) + role = Role.objects.filter(key=role_key, status=True).first() or Role.objects.filter(key=role_key).first() + if not role: + raise ValidationError( + detail={"supplier_role": [f'未找到系统角色 key="{role_key}",请先在「角色管理」中维护']} + ) + + plain = _default_system_password_plain() + md5_password = hashlib.md5(plain.encode("utf-8")).hexdigest() + hashed = make_password(md5_password) + + is_active = bool(supplier_user.status) + + user = Users( + username=email, + email=email, + name=(supplier_user.user_name or "").strip() or email, + mobile=(supplier_user.user_phone or "").strip() or "", + gender=0, + dept=dept, + dept_belong_id=str(dept.id) if dept.id is not None else None, + is_active=is_active, + ) + user.password = hashed + if request and getattr(request, "user", None) and str(request.user) != "AnonymousUser": + user.creator = request.user + + try: + user.save() + except IntegrityError as e: + raise ValidationError( + detail={"user_email": ["该邮箱已作为系统用户账号存在(并发冲突),请重试或更换邮箱"]} + ) from e + + user.role.add(role) + if dept.id is not None and not user.manage_dept.filter(pk=dept.pk).exists(): + user.manage_dept.add(dept.id) + + +class SupplierUserSerializer(CustomModelSerializer): + """供应商用户信息""" + + class Meta: + model = SupplierUser + fields = [f.name for f in SupplierUser._meta.concrete_fields] + read_only_fields = ["id"] + + +class SupplierUserCreateUpdateSerializer(CustomModelSerializer): + """供应商用户信息 创建/更新""" + + class Meta: + model = SupplierUser + fields = "__all__" + + +class SupplierSerializer(CustomModelSerializer): + """供应商信息""" + + class Meta: + model = Supplier + fields = [f.name for f in Supplier._meta.concrete_fields] + read_only_fields = ["id"] + + +class SupplierCreateUpdateSerializer(CustomModelSerializer): + """供应商信息 创建/更新""" + + supplier_id = serializers.CharField(max_length=20) + supplier_name = serializers.CharField(max_length=100) + supplier_short_name = serializers.CharField(max_length=50) + contact_person = serializers.CharField(max_length=20) + contact_phone = serializers.CharField(max_length=20) + contact_email = serializers.CharField(max_length=50) + country = serializers.CharField(max_length=20) + province = serializers.CharField(max_length=20) + + class Meta: + model = Supplier + fields = "__all__" + + +class SupplierViewSet(CustomModelViewSet): + """供应商信息管理接口""" + + queryset = Supplier.objects.all() + serializer_class = SupplierSerializer + create_serializer_class = SupplierCreateUpdateSerializer + update_serializer_class = SupplierCreateUpdateSerializer + search_fields = ["supplier_name", "supplier_short_name", "supplier_id", "company_code"] + ordering = ["-create_datetime"] + + +class SupplierUserViewSet(CustomModelViewSet): + """供应商用户信息(独立表;按 supplier_id 筛选可得到某供应商下全部联系人)。新建时在系统用户表同步创建账号。""" + + queryset = SupplierUser.objects.all() + serializer_class = SupplierUserSerializer + create_serializer_class = SupplierUserCreateUpdateSerializer + update_serializer_class = SupplierUserCreateUpdateSerializer + filter_fields = ["supplier_id", "supplier_role", "status", "user_email"] + search_fields = ["supplier_id", "supplier_name", "user_email", "user_name", "user_phone"] + ordering = ["-create_datetime", "id"] + + def perform_create(self, serializer): + with transaction.atomic(): + instance = serializer.save() + _sync_create_system_user_for_supplier(instance, self.request) diff --git a/backend/apps/pisadmin/basicinfo/views/system_no_rule.py b/backend/apps/pisadmin/basicinfo/views/system_no_rule.py new file mode 100644 index 0000000..ab3ce51 --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/system_no_rule.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.response import Response +from django.utils import timezone + +from apps.pisadmin.basicinfo.models import SystemNoRule +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + +# 由服务端维护,禁止前端随表单覆盖(避免取号后被编辑保存冲掉) +_SEQUENCE_FIELDS_FROM_CLIENT_FORBIDDEN = ( + "sequence_date", + "prev_sequence", + "current_sequence", + "last_generate_user", + "last_generate_time", +) + + +class SystemNoRuleSerializer(CustomModelSerializer): + """系统单号规则-序列化器""" + + class Meta: + model = SystemNoRule + fields = "__all__" + read_only_fields = ["id"] + + +class SystemNoRuleCreateUpdateSerializer(CustomModelSerializer): + """系统单号规则 创建/更新""" + + company_code = serializers.CharField(max_length=20) + rule_code = serializers.CharField(max_length=50) + reset_cycle = serializers.ChoiceField(choices=SystemNoRule.RESET_CYCLE_CHOICES) + seq_length = serializers.IntegerField(min_value=1, max_value=20) + + class Meta: + model = SystemNoRule + fields = "__all__" + + def validate(self, attrs): + company_code = attrs.get("company_code") + if company_code is None and getattr(self.instance, "company_code", None) is not None: + company_code = self.instance.company_code + rule_code = attrs.get("rule_code") + if rule_code is None and getattr(self.instance, "rule_code", None) is not None: + rule_code = self.instance.rule_code + instance_id = getattr(self.instance, "id", None) + if company_code is not None and rule_code is not None: + exists = SystemNoRule.objects.filter(company_code=company_code, rule_code=rule_code) + if instance_id: + exists = exists.exclude(id=instance_id) + if exists.exists(): + raise serializers.ValidationError("同一厂区下的生成单据标识号不可重复") + return attrs + + def create(self, validated_data): + for k in _SEQUENCE_FIELDS_FROM_CLIENT_FORBIDDEN: + validated_data.pop(k, None) + rc = validated_data["reset_cycle"] + validated_data["sequence_date"] = SystemNoRule.format_sequence_date(rc) + validated_data["prev_sequence"] = 0 + validated_data["current_sequence"] = 1 + return super().create(validated_data) + + def update(self, instance, validated_data): + for k in _SEQUENCE_FIELDS_FROM_CLIENT_FORBIDDEN: + validated_data.pop(k, None) + new_cycle = validated_data.get("reset_cycle") + if new_cycle is not None and new_cycle != instance.reset_cycle: + validated_data["sequence_date"] = SystemNoRule.format_sequence_date(new_cycle) + validated_data["prev_sequence"] = 0 + validated_data["current_sequence"] = 1 + return super().update(instance, validated_data) + + +class SystemNoRuleViewSet(CustomModelViewSet): + """系统单号规则管理接口""" + + queryset = SystemNoRule.objects.all() + serializer_class = SystemNoRuleSerializer + create_serializer_class = SystemNoRuleCreateUpdateSerializer + update_serializer_class = SystemNoRuleCreateUpdateSerializer + search_fields = ["company_code", "rule_code", "factory_code", "prefix"] + ordering = ["-create_datetime"] + + @action(methods=["post"], detail=False, url_path="generate_code") + def generate_code(self, request): + """生成单据号,含并发锁保证流水唯一(与 `SystemNoRule.allocate_system_number` 同源逻辑)""" + + company_code = request.data.get("company_code") + rule_code = request.data.get("rule_code") + if not company_code or not rule_code: + return Response({"success": False, "message": "参数缺失:company_code 与 rule_code 必填", "code": None}, status=400) + + username = getattr(request.user, "username", None) or getattr(request.user, "name", None) + try: + code = SystemNoRule.allocate_system_number( + company_code, + rule_code, + username=username, + now=timezone.now(), + ) + except serializers.ValidationError as exc: + detail = getattr(exc, "detail", None) + if isinstance(detail, list) and detail: + msg = "; ".join(str(x) for x in detail) + elif isinstance(detail, dict): + msg = "; ".join(f"{k}: {v}" for k, v in detail.items()) + else: + msg = str(detail) if detail is not None else str(exc) + return Response({"success": False, "message": msg, "code": None}, status=400) + except Exception as exc: # pragma: no cover - defensive + return Response({"success": False, "message": str(exc), "code": None}, status=500) + + return Response({"success": True, "message": "", "code": code}) diff --git a/backend/apps/pisadmin/basicinfo/views/unit.py b/backend/apps/pisadmin/basicinfo/views/unit.py new file mode 100644 index 0000000..626fe9d --- /dev/null +++ b/backend/apps/pisadmin/basicinfo/views/unit.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from rest_framework import serializers + +from apps.pisadmin.basicinfo.models import Unit +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class UnitSerializer(CustomModelSerializer): + """计量单位-序列化器""" + + class Meta: + model = Unit + fields = "__all__" + read_only_fields = ["id"] + + +class UnitCreateUpdateSerializer(CustomModelSerializer): + """计量单位 创建/更新""" + + unitname = serializers.CharField(max_length=50) + unitcode = serializers.CharField(max_length=20, allow_blank=True, required=False) + + class Meta: + model = Unit + fields = "__all__" + + +class UnitViewSet(CustomModelViewSet): + """计量单位管理接口""" + + queryset = Unit.objects.all() + serializer_class = UnitSerializer + create_serializer_class = UnitCreateUpdateSerializer + update_serializer_class = UnitCreateUpdateSerializer + search_fields = ["unitname", "unitcode"] + ordering = ["-create_datetime"] diff --git a/backend/apps/pisadmin/dashboard/__init__.py b/backend/apps/pisadmin/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/pisadmin/dashboard/apps.py b/backend/apps/pisadmin/dashboard/apps.py new file mode 100644 index 0000000..3569efc --- /dev/null +++ b/backend/apps/pisadmin/dashboard/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.pisadmin.dashboard' diff --git a/backend/apps/pisadmin/dashboard/serializers.py b/backend/apps/pisadmin/dashboard/serializers.py new file mode 100644 index 0000000..cab1219 --- /dev/null +++ b/backend/apps/pisadmin/dashboard/serializers.py @@ -0,0 +1,85 @@ +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() + method = serializers.SerializerMethodField() + quote_deadline = serializers.DateTimeField(allow_null=True, required=False) + bid_start_time = serializers.DateTimeField(allow_null=True, required=False) + bid_end_time = serializers.DateTimeField(allow_null=True, required=False) + + def get_method(self, obj): + if obj.get('buying_method') == 2: + return '招标' + return '询价' + + +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() + unit = serializers.CharField() + quantity = serializers.CharField(allow_blank=True, required=False) + status = serializers.IntegerField() + method = serializers.SerializerMethodField() + quote_deadline = serializers.DateTimeField(allow_null=True, required=False) + bid_start_time = serializers.DateTimeField(allow_null=True, required=False) + bid_end_time = serializers.DateTimeField(allow_null=True, required=False) + + def get_method(self, obj): + if obj.get('buying_method') == 2: + return '招标' + return '询价' + + +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): + day = serializers.IntegerField(required=False) + month = serializers.CharField(required=False) + 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) diff --git a/backend/apps/pisadmin/dashboard/urls.py b/backend/apps/pisadmin/dashboard/urls.py new file mode 100644 index 0000000..4587499 --- /dev/null +++ b/backend/apps/pisadmin/dashboard/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from .views import DashboardView + +urlpatterns = [ + path('', DashboardView.as_view(), name='dashboard'), +] diff --git a/backend/apps/pisadmin/dashboard/views.py b/backend/apps/pisadmin/dashboard/views.py new file mode 100644 index 0000000..d148498 --- /dev/null +++ b/backend/apps/pisadmin/dashboard/views.py @@ -0,0 +1,513 @@ +from datetime import timedelta + +from django.utils import timezone +from django.db.models import Count, F +from django.db.models.functions import TruncMonth, TruncDate +from rest_framework import serializers, views +from rest_framework.response import Response +from dvadmin.utils.json_response import SuccessResponse + +from apps.pisadmin.miscprocurement.models import Inquiry +from apps.pissupplier.models import QuotationMaster +from apps.pisadmin.basicinfo.models import Supplier + + +class BuyerKPISerializer(serializers.Serializer): + total_inquiries = serializers.IntegerField() + pending_inquiries = serializers.IntegerField() + completed_quotes = serializers.IntegerField() + total_suppliers = serializers.IntegerField() + quote_timely_rate = serializers.FloatField() + + +class BuyerTaskSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField() + inquiry_no = serializers.CharField() + status = serializers.CharField() + created_at = serializers.DateTimeField() + # 须声明,否则校验时会被丢弃,SerializerMethodField 无法读到库里的采购方式 + buying_method = serializers.IntegerField(required=False, allow_null=True) + method = serializers.SerializerMethodField() + quote_deadline = serializers.DateTimeField(allow_null=True, required=False) + bid_start_time = serializers.DateTimeField(allow_null=True, required=False) + bid_end_time = serializers.DateTimeField(allow_null=True, required=False) + + def get_method(self, obj): + bm = obj.get('buying_method') + if bm == 2: + return '招标' + return '询价' + + +class SupplierKPISerializer(serializers.Serializer): + total_quotes = serializers.IntegerField() + pending_quotes = serializers.IntegerField() + won_quotes = serializers.IntegerField() + conversion_rate = serializers.FloatField() + quote_timely_rate = serializers.FloatField() + on_time_quotes = serializers.IntegerField() + overdue_quotes = serializers.IntegerField() + + +class SupplierQuoteSerializer(serializers.Serializer): + id = serializers.IntegerField() + inquiry_no = serializers.CharField() + item_name = serializers.CharField() + unit = serializers.CharField() + quantity = serializers.CharField(allow_blank=True, required=False) + status = serializers.IntegerField() + buying_method = serializers.IntegerField(required=False, allow_null=True) + method = serializers.SerializerMethodField() + quote_deadline = serializers.DateTimeField(allow_null=True, required=False) + bid_start_time = serializers.DateTimeField(allow_null=True, required=False) + bid_end_time = serializers.DateTimeField(allow_null=True, required=False) + + def get_method(self, obj): + bm = obj.get('buying_method') + if bm == 2: + return '招标' + return '询价' + + +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(required=False) + day = serializers.IntegerField(required=False) + 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) + + +class DashboardView(views.APIView): + """看板数据视图""" + + def get_buyer_kpi(self, 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() + + # 供应商报价及时率 = 在截止时间前提交的报价数 / 总报价数 + buyer_quotations = QuotationMaster.objects.filter( + inquiry_no__in=Inquiry.objects.filter( + create_user=user.username + ).values('inquiry_no') + ) + total_quotes = buyer_quotations.count() + timely_quotes = buyer_quotations.filter( + quotetime__isnull=False, + quote_deadline__isnull=False, + quotetime__lte=F('quote_deadline') + ).count() + quote_timely_rate = round(timely_quotes / total_quotes * 100, 1) if total_quotes > 0 else 0.0 + + return { + 'total_inquiries': total_inquiries, + 'pending_inquiries': pending_inquiries, + 'completed_quotes': completed_quotes, + 'total_suppliers': total_suppliers, + 'quote_timely_rate': quote_timely_rate, + } + + def get_buyer_tasks(self, user): + """采购方待办任务(仅询价状态为发布/报价中/报价结束/比议价中的单,见 Inquiry.BUYER_DASHBOARD_TASK_STATUSES)。""" + inquiries = Inquiry.objects.filter( + create_user=user.username, + status__in=Inquiry.BUYER_DASHBOARD_TASK_STATUSES, + ).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, + 'buying_method': i.buying_method, + 'quote_deadline': i.quote_deadline, + 'bid_start_time': i.bid_start_time, + 'bid_end_time': i.bid_end_time, + } + for i in inquiries + ] + + def get_supplier_kpi(self, user): + """供应商 KPI 计算""" + # 超级管理员看到所有供应商汇总数据 + if user.is_superuser: + total_quotes = QuotationMaster.objects.count() + pending_quotes = QuotationMaster.objects.filter( + status__in=QuotationMaster.DASHBOARD_PENDING_STATUSES + ).count() + won_quotes = QuotationMaster.objects.filter(is_awarded=1).count() + else: + total_quotes = QuotationMaster.objects.filter(supplier_code=user.username).count() + pending_quotes = QuotationMaster.objects.filter( + supplier_code=user.username, status__in=QuotationMaster.DASHBOARD_PENDING_STATUSES + ).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 + + # 及时率计算 + if user.is_superuser: + all_quotes = QuotationMaster.objects.all() + else: + all_quotes = QuotationMaster.objects.filter(supplier_code=user.username) + total_with_deadline = all_quotes.filter(quote_deadline__isnull=False).count() + on_time_quotes = all_quotes.filter( + quotetime__isnull=False, + quote_deadline__isnull=False, + quotetime__lte=F('quote_deadline') + ).count() + overdue_quotes = total_with_deadline - on_time_quotes + quote_timely_rate = round(on_time_quotes / total_with_deadline * 100, 1) if total_with_deadline > 0 else 0.0 + + return { + 'total_quotes': total_quotes, + 'pending_quotes': pending_quotes, + 'won_quotes': won_quotes, + 'conversion_rate': round(conversion_rate, 2), + 'quote_timely_rate': quote_timely_rate, + 'on_time_quotes': on_time_quotes, + 'overdue_quotes': overdue_quotes, + } + + def _supplier_quote_remaining_sort_key(self, q, inquiry, now): + """用于待报价清单排序:剩余时间由少到多(越早越靠前);无截止时间排最后。""" + bm = (inquiry.buying_method if inquiry is not None else None) or q.buying_method + if bm == 2: + start, end = q.bid_start_time, q.bid_end_time + if not start: + return None + if now < start: + return (start - now).total_seconds() + if end and now < end: + return (end - now).total_seconds() + return float('inf') + if q.quote_deadline: + return (q.quote_deadline - now).total_seconds() + return None + + def get_supplier_pending_quotes(self, user): + """供应商待报价清单:仅未报价(待报价)/报价中;按剩余时间由少到多取前 10 条。""" + base = QuotationMaster.objects.filter(status__in=QuotationMaster.DASHBOARD_PENDING_STATUSES) + if not user.is_superuser: + base = base.filter(supplier_code=user.username) + quotes = list(base) + if not quotes: + return [] + + now = timezone.now() + inquiry_nos = {q.inquiry_no for q in quotes} + inquiries = {i.inquiry_no: i for i in Inquiry.objects.filter(inquiry_no__in=inquiry_nos)} + + def sort_key(q): + inq = inquiries.get(q.inquiry_no) + k = self._supplier_quote_remaining_sort_key(q, inq, now) + if k is None: + return float('inf') + return k + + quotes.sort(key=sort_key) + quotes = quotes[:10] + + from apps.pisadmin.miscprocurement.models import InquiryRfqItem + + result = [] + for q in quotes: + inquiry = inquiries.get(q.inquiry_no) + quantity = '' + unit = '' + if inquiry: + item = InquiryRfqItem.objects.filter(inquiry_no=q.inquiry_no).first() + if item: + quantity = item.qty + unit = item.unit or '' + result.append({ + 'id': q.autoid, + 'inquiry_no': q.inquiry_no, + 'item_name': inquiry.title if inquiry else '', + 'quantity': quantity, + 'unit': unit, + 'status': q.status, + 'buying_method': (inquiry.buying_method if inquiry is not None else q.buying_method), + 'quote_deadline': q.quote_deadline, + 'bid_start_time': q.bid_start_time, + 'bid_end_time': q.bid_end_time, + }) + return result + + def get_trend_data(self, 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') + ) + return [ + {'month': d['month'].strftime('%Y-%m'), 'count': d['count']} + for d in data + ] + else: + # 超级管理员看到所有供应商汇总趋势 + if user.is_superuser: + data = ( + QuotationMaster.objects.filter(creattime__gte=six_months_ago) + .annotate(month=TruncMonth('creattime')) + .values('month') + .annotate(quotes=Count('autoid')) + .order_by('month') + ) + result = [] + for d in data: + won = QuotationMaster.objects.filter( + 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 + else: + data = ( + QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__gte=six_months_ago + ) + .annotate(month=TruncMonth('creattime')) + .values('month') + .annotate(quotes=Count('autoid')) + .order_by('month') + ) + 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 + + def get_buyer_trend_daily(self, user): + """获取采购方当月每日趋势数据""" + now = timezone.now() + start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + + data = ( + Inquiry.objects.filter( + create_user=user.username, + create_time__gte=start_of_month, + create_time__lte=now + ) + .annotate(day=TruncDate('create_time')) + .values('day') + .annotate(count=Count('id')) + .order_by('day') + ) + return [ + {'day': d['day'].day, 'count': d['count']} + for d in data + ] + + def get_supplier_trend_daily(self, user): + """获取供应商当月每日趋势数据""" + now = timezone.now() + start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + + if user.is_superuser: + data = ( + QuotationMaster.objects.filter( + creattime__gte=start_of_month, + creattime__lte=now + ) + .annotate(day=TruncDate('creattime')) + .values('day') + .annotate(quotes=Count('autoid')) + .order_by('day') + ) + result = [] + for d in data: + won = QuotationMaster.objects.filter( + creattime__day=d['day'].day, + creattime__month=now.month, + creattime__year=now.year, + is_awarded=1 + ).count() + result.append({ + 'day': d['day'].day, + 'quotes': d['quotes'], + 'won': won, + }) + return result + else: + data = ( + QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__gte=start_of_month, + creattime__lte=now + ) + .annotate(day=TruncDate('creattime')) + .values('day') + .annotate(quotes=Count('autoid')) + .order_by('day') + ) + result = [] + for d in data: + won = QuotationMaster.objects.filter( + supplier_code=user.username, + creattime__day=d['day'].day, + creattime__month=now.month, + creattime__year=now.year, + is_awarded=1 + ).count() + result.append({ + 'day': d['day'].day, + 'quotes': d['quotes'], + 'won': won, + }) + return result + + def get_messages(self, user): + """消息通知 - 占位实现,后续接入通知系统""" + return [] + + def get_buyer_data(self, user): + """获取采购方看板数据""" + return { + 'kpi': self.get_buyer_kpi(user), + 'tasks': self.get_buyer_tasks(user), + 'messages': self.get_messages(user), + 'trend': self.get_buyer_trend_daily(user), + } + + def get_supplier_data(self, user): + """获取供应商看板数据""" + return { + 'kpi': self.get_supplier_kpi(user), + 'pending_quotes': self.get_supplier_pending_quotes(user), + 'messages': self.get_messages(user), + 'trend': self.get_supplier_trend_daily(user), + } + + def get(self, request): + """获取看板数据 + + Returns role-based dashboard data based on user's permissions. + - Buyer role: returns buyer dashboard data + - Supplier role: returns supplier dashboard data + - Both roles: returns both + - Superadmin or no business data: returns empty structure + """ + user = request.user + if not user or not user.is_authenticated: + return Response({'buyer': None, 'supplier': None}) + + # 分别判断用户角色,避免一个表不存在影响另一个 + has_supplier_role = False + has_buyer_role = False + + try: + has_supplier_role = QuotationMaster.objects.filter( + supplier_code=user.username + ).exists() + except Exception: + pass + + try: + has_buyer_role = Inquiry.objects.filter( + create_user=user.username + ).exists() + except Exception: + pass + + # 超级管理员或有任何角色,始终返回对应看板数据结构 + buyer_data = None + supplier_data = None + + # 采购方看板:超级管理员或有任何采购记录的用户 + if user.is_superuser or has_buyer_role: + try: + buyer_data = self.get_buyer_data(user) + print(f"[DEBUG] get_buyer_data success, tasks count: {len(buyer_data.get('tasks', []))}") + except Exception as e: + print(f"[DEBUG] get_buyer_data exception: {e}") + import traceback + traceback.print_exc() + buyer_data = { + 'kpi': {'total_inquiries': 0, 'pending_inquiries': 0, 'completed_quotes': 0, 'total_suppliers': 0, 'quote_timely_rate': 0}, + 'tasks': [], + 'messages': [], + 'trend': [], + } + + # 供应商看板:超级管理员或有任何供应商记录的用户 + if user.is_superuser or has_supplier_role: + try: + supplier_data = self.get_supplier_data(user) + except Exception: + supplier_data = { + 'kpi': {'total_quotes': 0, 'pending_quotes': 0, 'won_quotes': 0, 'conversion_rate': 0}, + 'pending_quotes': [], + 'messages': [], + 'trend': [], + } + + response_data = {'buyer': buyer_data, 'supplier': supplier_data} + + # 使用序列化器验证响应数据 + serializer = DashboardResponseSerializer(data=response_data) + serializer.is_valid(raise_exception=True) + + # 须用 .data:validated_data 不含 SerializerMethodField(如 tasks[].method) + return SuccessResponse(data=serializer.data, msg="获取成功") diff --git a/backend/apps/pisadmin/miscprocurement/__init__.py b/backend/apps/pisadmin/miscprocurement/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/apps/pisadmin/miscprocurement/admin.py b/backend/apps/pisadmin/miscprocurement/admin.py new file mode 100644 index 0000000..12fd819 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/admin.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin + +from apps.pisadmin.miscprocurement.models import ( + MiscProcMaterial, + MiscProcurementMaterialInfo, + MiscProcurementStationInfo, +) + + +@admin.register(MiscProcurementMaterialInfo) +class MiscProcurementMaterialInfoAdmin(admin.ModelAdmin): + list_display = ["materialtype", "factory", "density", "price", "status"] + search_fields = ["materialtype", "factory"] + + +@admin.register(MiscProcurementStationInfo) +class MiscProcurementStationInfoAdmin(admin.ModelAdmin): + list_display = ["stationname", "stationcode", "company_code", "stationtype", "rate", "status"] + search_fields = ["stationname", "stationcode", "company_code"] + + +@admin.register(MiscProcMaterial) +class MiscProcMaterialAdmin(admin.ModelAdmin): + list_display = ["partid", "partid_name", "company_code", "status"] + search_fields = ["partid", "partid_name", "company_code"] diff --git a/backend/apps/pisadmin/miscprocurement/apps.py b/backend/apps/pisadmin/miscprocurement/apps.py new file mode 100644 index 0000000..c28c462 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ProcurementConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.pisadmin.miscprocurement" + verbose_name = "杂采管理" diff --git a/backend/apps/pisadmin/miscprocurement/cost_template_builder.py b/backend/apps/pisadmin/miscprocurement/cost_template_builder.py new file mode 100644 index 0000000..d37ef44 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/cost_template_builder.py @@ -0,0 +1,345 @@ +""" +成本结构模板构建工具 + +负责将前端发送的分层结构化 sections 数据展开并逐条持久化到数据库。 + +数据流: + 前端 SectionBuilder 发送 → sections 数组 + ├─ section 1 (title: "材料成本") + │ └─ fields 数组 + │ ├─ field 1 (key: "material", nameCn: "材质") + │ ├─ field 2 (key: "length", nameCn: "长") + │ └─ field 3 (key: "unitPrice", nameCn: "单价") + ├─ section 2 (title: "加工成本") + │ └─ fields 数组 + │ ├─ field 1 (key: "process_station", nameCn: "加工工站") + │ └─ field 2 (key: "process_price", nameCn: "加工费") + └─ section 3 (title: "利润") + └─ fields 数组 + └─ field 1 (key: "profit_rate", nameCn: "利润率") + +本工具将上述结构展开为 CostEstimateTemplateBody 表的独立行: + item_order: 全局行号(1, 2, 3, ..., 6) + item_no: 字段 key(material, length, unitPrice, ...) + item_name_cn: 字段中文名称(材质, 长, 单价, ...) + item_name_en: 字段英文名称 + item_name_vn: 字段越南名称 + cost_category: section 对应的成本类别(1=材料成本, 2=加工成本, 5=利润, ...) + is_fixed: 字段是否固定(1=固定, 0=变量) + is_computed: 是否自动计算(1=是, 0=否) + purchaser_required: 采购人是否必填(1=是, 0=否) + supplier_required: 供应商操作(0-6) + remark: 字段备注 + +说明:独立成本模板在 PIS 中「新版本」派生使用 serializers.create_cost_template_new_version(同 template_no、 +version 递增;若提交明细与源版本规范化后一致则拒绝创建);确认新版本时由 CostEstimateTemplateViewSet.confirm +将同编号更低版本主表行置为作废(2)。本模块将前端 sections 展开后写入 `CostEstimateTemplateBody`。 +""" + +from typing import Dict, List, Optional, Tuple +from django.utils import timezone +from apps.pisadmin.miscprocurement.models import CostEstimateTemplateHead, CostEstimateTemplateBody + + +class CostTemplateBuilder: + """成本模板构建器:负责将前端sections数据展开持久化到数据库""" + + SUPPLIER_BEHAVIOR_TO_CODE = { + "prefill_locked": 1, + "prefill_editable": 2, + "hidden_required": 3, + "hidden_optional": 4, + "required": 5, + "optional": 6, + } + + # Section title 与 cost_category 的映射表 + COST_CATEGORY_MAPPING = { + "材料成本": "1", + "加工成本": "2", + "其它成本": "3", + "其他成本": "3", + "管销研费用": "4", + "管销研": "4", + "利润": "5", + "税金": "6", + "税费成本": "6", + "产品明细": "7", + } + + @classmethod + def get_cost_category_from_title(cls, title: str) -> str: + """ + 根据 section 的 title 获取对应的 cost_category 代码 + + Args: + title: section 的标题(如 "材料成本"、"加工成本") + + Returns: + cost_category 代码(字符串,"1" 到 "7") + """ + return cls.COST_CATEGORY_MAPPING.get(title, "1") + + @classmethod + def normalize_supplier_required( + cls, + value, + purchaser_required: int = 0, + supplier_editable=None, + is_computed: int = 0, + ) -> int: + if is_computed: + return 0 + if isinstance(value, str): + text = value.strip() + if not text: + return 0 + if text in cls.SUPPLIER_BEHAVIOR_TO_CODE: + return cls.SUPPLIER_BEHAVIOR_TO_CODE[text] + try: + code = int(text) + if 0 <= code <= 6: + return code + except ValueError: + pass + elif isinstance(value, bool): + if purchaser_required: + if value: + return 2 if supplier_editable else 1 + return 4 if supplier_editable else 3 + return 5 if value else 6 + elif value is not None: + try: + code = int(value) + if 0 <= code <= 6: + return code + except (TypeError, ValueError): + pass + + if purchaser_required: + if supplier_editable is True: + return 4 + if supplier_editable is False: + return 3 + return 0 + + @classmethod + def normalize_purchaser_required(cls, value, is_computed: int = 0) -> int: + if is_computed: + return 0 + if value in (None, ""): + return 0 + if isinstance(value, bool): + return 1 if value else 0 + try: + return 1 if int(value) else 0 + except (TypeError, ValueError): + if isinstance(value, str): + return 1 if value.strip().lower() in ("true", "yes", "y", "on") else 0 + return 0 + + @classmethod + def build_body_records_from_sections( + cls, + sections: Optional[List[Dict]] + ) -> List[Tuple[Dict, str]]: + """ + 将前端发送的 sections 数据展开成 body 记录列表 + + Args: + sections: 前端发送的 sections 数据(来自 SectionBuilder) + + Returns: + list of (record_dict, cost_category) 元组 + 其中 record_dict 包含所有需要存储到 CostEstimateTemplateBody 的字段 + """ + if not sections: + return [] + + body_records = [] + global_item_order = 1 # 全局行号 + + for section in sections: + if not isinstance(section, dict): + continue + + section_title = section.get("title") or section.get("name", "") + cost_category = cls.get_cost_category_from_title(section_title) + fields = section.get("fields", []) + + if not fields: + # 旧格式兼容:section 本身是一个 item + body_records.append(( + { + "cost_category": section.get("cost_category", cost_category), + "item_order": section.get("item_order", global_item_order), + "item_no": section.get("item_no", str(global_item_order)), + "item_name_cn": section.get("item_name_cn", ""), + "item_name_en": section.get("item_name_en", ""), + "item_name_vn": section.get("item_name_vn", ""), + "is_fixed": int(section.get("is_fixed", section.get("item_category", 0)) or 0), + "is_computed": int(section.get("is_computed", 0) or 0), + "purchaser_required": cls.normalize_purchaser_required( + section.get("purchaser_required", 0), + is_computed=int(section.get("is_computed", 0) or 0), + ), + "supplier_required": cls.normalize_supplier_required( + section.get("supplier_required", section.get("supplier_behavior")), + purchaser_required=cls.normalize_purchaser_required( + section.get("purchaser_required", 0), + is_computed=int(section.get("is_computed", 0) or 0), + ), + supplier_editable=section.get("supplier_editable"), + is_computed=int(section.get("is_computed", 0) or 0), + ), + "remark": section.get("remark"), + }, + cost_category + )) + global_item_order += 1 + else: + # 新格式:迭代 section 中的 fields,逐条创建明细 + for field_idx, field in enumerate(fields, start=1): + if not isinstance(field, dict): + continue + + # ============ 提取字段信息 ============ + field_key = field.get("key", "") + field_label = field.get("label", "") + field_name_cn = field.get("nameCn") or field_label or "" + field_name_en = field.get("nameEn", "") + field_name_vn = field.get("nameVn", "") + remark = field.get("remark", "") + + # ============ 判断字段属性 ============ + # 自动计算:根据 autoFill 标志判断 + is_computed = 1 if field.get("autoFill") else 0 + + # 采购人必填 + purchaser_required = cls.normalize_purchaser_required(field.get("purchaserRequired"), is_computed=is_computed) + + supplier_required = cls.normalize_supplier_required( + field.get("supplier_required", field.get("supplierBehavior")), + purchaser_required=purchaser_required, + supplier_editable=field.get("supplierEditable"), + is_computed=is_computed, + ) + if supplier_required == 0 and "supplierRequired" in field: + supplier_required = cls.normalize_supplier_required( + field.get("supplierRequired"), + purchaser_required=purchaser_required, + supplier_editable=field.get("supplierEditable"), + is_computed=is_computed, + ) + + # 固定字段:fixed=True 表示固定字段(is_fixed=1) + is_fixed = 1 if field.get("fixed") else 0 + + # ============ 构建记录 ============ + record = { + "cost_category": cost_category, + "item_order": global_item_order, + "item_no": field_key or str(field_idx), + "item_name_cn": field_name_cn, + "item_name_en": field_name_en, + "item_name_vn": field_name_vn, + "is_fixed": is_fixed, + "is_computed": is_computed, + "purchaser_required": purchaser_required, + "supplier_required": supplier_required, + "remark": remark, + } + + body_records.append((record, cost_category)) + global_item_order += 1 + + return body_records + + @classmethod + def create_cost_template_body_records( + cls, + template_head: CostEstimateTemplateHead, + sections: Optional[List[Dict]], + create_user: Optional[str] = None, + create_time: Optional[object] = None + ) -> int: + """ + 根据 sections 数据逐条创建成本模板明细(body 记录) + + Args: + template_head: 成本模板主表记录 + sections: 前端发送的 sections 数据 + create_user: 创建人用户名(可选,如果为None则不设置) + create_time: 创建时间(可选,如果为None则使用当前时间) + + Returns: + 创建的记录数 + """ + body_records = cls.build_body_records_from_sections(sections) + + if not body_records: + return 0 + + # 如果未提供,使用当前时间 + if create_time is None: + create_time = timezone.now() + + # 批量创建 body 记录 + head_ver = getattr(template_head, "version", None) + if head_ver is None: + head_ver = 1 + bulk_objects = [ + CostEstimateTemplateBody( + template_no=template_head.template_no, + version=head_ver, + create_user=create_user, + create_time=create_time, + update_user=create_user, + update_time=create_time, + **record_dict + ) + for record_dict, _ in body_records + ] + + created_records = CostEstimateTemplateBody.objects.bulk_create(bulk_objects) + return len(created_records) + + @classmethod + def debug_print_sections_expansion( + cls, + sections: Optional[List[Dict]] + ) -> None: + """ + 调试函数:打印 sections 展开过程 + 用于验证数据转换逻辑是否正确 + """ + if not sections: + print("❌ sections 为空") + return + + body_records = cls.build_body_records_from_sections(sections) + + print(f"\n📊 sections 展开结果:共 {len(body_records)} 条明细") + print("=" * 100) + + for idx, (record, cost_category) in enumerate(body_records, start=1): + cost_category_display = cls.COST_CATEGORY_MAPPING.get( + [k for k, v in cls.COST_CATEGORY_MAPPING.items() if v == cost_category][0] if any( + k for k, v in cls.COST_CATEGORY_MAPPING.items() if v == cost_category + ) else "unknown", + "未知" + ) + + print(f"\n[条目 {idx}] 项次序号: {record['item_order']}") + print(f" 成本类别: {record['cost_category']} ({cost_category_display})") + print(f" 项号: {record['item_no']}") + print(f" 中文名: {record['item_name_cn']}") + print(f" 英文名: {record['item_name_en']}") + print(f" 越南名: {record['item_name_vn']}") + print(f" 固定字段: {record['is_fixed']} {'✓' if record['is_fixed'] else '✗'}") + print(f" 自动计算: {record['is_computed']} {'✓' if record['is_computed'] else '✗'}") + print(f" 采购必填: {record['purchaser_required']} {'✓' if record['purchaser_required'] else '✗'}") + print(f" 供应商操作: {record['supplier_required']}") + print(f" 备注: {record['remark']}") + + print("\n" + "=" * 100) diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0001_initial.py b/backend/apps/pisadmin/miscprocurement/migrations/0001_initial.py new file mode 100644 index 0000000..88087ba --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0001_initial.py @@ -0,0 +1,354 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CostEstimateTemplateBody', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cost_category', models.CharField(choices=[('1', '材料成本'), ('2', '加工成本'), ('3', '其它成本'), ('4', '管销研成本'), ('5', '利润成本'), ('6', '税费成本'), ('7', '产品明细')], db_column='CostCategory', max_length=4, verbose_name='成本类别')), + ('item_order', models.IntegerField(db_column='ItemOrder', verbose_name='排序序号')), + ('item_no', models.CharField(db_column='ItemNo', max_length=100, verbose_name='项次编号')), + ('item_name_cn', models.CharField(db_column='ItemName_cn', max_length=100, verbose_name='中文名称')), + ('item_name_en', models.CharField(blank=True, db_column='ItemName_en', max_length=100, null=True, verbose_name='英文名称')), + ('item_name_vn', models.CharField(blank=True, db_column='ItemName_vn', max_length=100, null=True, verbose_name='越南语名称')), + ('is_fixed', models.IntegerField(db_column='is_fixed', verbose_name='是否固定字段:1是0否')), + ('is_computed', models.IntegerField(db_column='is_computed', verbose_name='是否系统自动计算/带出')), + ('purchaser_required', models.IntegerField(db_column='PurchaserRequired', verbose_name='采购必填否(1/0)')), + ('supplier_required', models.IntegerField(choices=[(0, ' '), (1, '带出不可修改'), (2, '带出可修改'), (3, '不带出必填'), (4, '不带出可空'), (5, '必填'), (6, '可空')], db_column='SupplierRequired', verbose_name='供应商操作')), + ('remark', models.CharField(blank=True, db_column='Remark', max_length=200, null=True, verbose_name='备注')), + ('create_user', models.CharField(blank=True, db_column='CreateUser', max_length=20, null=True, verbose_name='创建人')), + ('create_time', models.DateTimeField(blank=True, db_column='CreateTime', null=True, verbose_name='创建时间')), + ('update_user', models.CharField(blank=True, db_column='UpdateUser', max_length=20, null=True, verbose_name='最后更新人')), + ('update_time', models.DateTimeField(blank=True, db_column='UpdateTime', null=True, verbose_name='最后更新时间')), + ], + options={ + 'verbose_name': '成本估算模板明细', + 'verbose_name_plural': '成本估算模板明细', + 'db_table': 't_CostEstimate_Template_Body', + 'ordering': ('item_order', 'id'), + }, + ), + migrations.CreateModel( + name='CostEstimateTemplateHead', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('template_no', models.CharField(db_column='TemplateNo', max_length=20, unique=True, verbose_name='模板编号')), + ('template_name', models.CharField(db_column='TemplateName', max_length=50, verbose_name='模板名称')), + ('procurement_category', models.CharField(db_column='ProcurementCategory', max_length=4, verbose_name='采购类别:1-策采;2-杂采')), + ('is_bom', models.CharField(db_column='IsBom', max_length=4, verbose_name='启用BOM否')), + ('acti', models.CharField(blank=True, db_column='Acti', default='Y', max_length=4, null=True, verbose_name='有效否:Y/N')), + ('template_desc', models.CharField(blank=True, db_column='TemplateDesc', max_length=500, null=True, verbose_name='模板描述')), + ('is_can_add_materials', models.IntegerField(db_column='IsCanAddMaterials', default=0, verbose_name='材料允许供应商增行')), + ('is_can_add_process', models.IntegerField(db_column='IsCanAddprocess', default=0, verbose_name='加工允许供应商增行')), + ('create_user', models.CharField(blank=True, db_column='CreateUser', max_length=20, null=True, verbose_name='创建人')), + ('create_time', models.DateTimeField(blank=True, db_column='CreateTime', null=True, verbose_name='创建时间')), + ('update_user', models.CharField(blank=True, db_column='UpdateUser', max_length=20, null=True, verbose_name='最后更新人')), + ('update_time', models.DateTimeField(blank=True, db_column='UpdateTime', null=True, verbose_name='最后更新时间')), + ], + options={ + 'verbose_name': '成本估算模板主表', + 'verbose_name_plural': '成本估算模板主表', + 'db_table': 't_CostEstimate_Template_Head', + 'ordering': ('-update_time', '-id'), + }, + ), + migrations.CreateModel( + name='Inquiry', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_index=True, max_length=20, unique=True, verbose_name='询价单号')), + ('title', models.CharField(max_length=20, verbose_name='询价单名称')), + ('purchase_type', models.IntegerField(choices=[(1, '策采'), (2, '杂采')], verbose_name='采购类别')), + ('material_type', models.CharField(blank=True, max_length=20, null=True, verbose_name='材料类型')), + ('template', models.CharField(help_text='对应成本估算模板编号;发布生成供应商报价单时,子表字段是否从询价单带入由该模板明细 is_computed=1 或 supplier_required 为 1/2 决定。', max_length=20, verbose_name='询价模版')), + ('is_bom', models.IntegerField(default=0, verbose_name='是否BOM否')), + ('currency', models.CharField(default='CNY', max_length=20, verbose_name='交易币别')), + ('company_code', models.CharField(blank=True, max_length=20, null=True, verbose_name='公司代码')), + ('purchase_dept', models.CharField(blank=True, max_length=20, null=True, verbose_name='采购部门')), + ('buyer', models.CharField(max_length=20, verbose_name='采购负责人')), + ('quote_deadline', models.DateTimeField(blank=True, null=True, verbose_name='报价截止时间')), + ('target_price', models.DecimalField(db_column='target_price', decimal_places=4, default=0, max_digits=12, verbose_name='目标价格')), + ('lead_time_days', models.IntegerField(default=0, verbose_name='交货周期(天)')), + ('payment_method', models.IntegerField(choices=[(1, '月结30天'), (2, '月结60天'), (3, '不到付款'), (4, '预付30%'), (5, '预付50%'), (6, '余款至生产'), (7, '价格审核'), (8, '价格结算(运费)'), (9, '新建(结束)')], verbose_name='付款方式')), + ('status', models.IntegerField(choices=[(1, '开立'), (2, '确认'), (3, '发布'), (4, '报价中'), (5, '报价结束'), (6, '比议价中'), (7, '价格审核'), (8, '核价通过(结束)'), (9, '落标(结束)'), (0, '作废')], db_index=True, default=1, verbose_name='状态')), + ('confirm_user', models.CharField(blank=True, max_length=20, null=True, verbose_name='确认人')), + ('confirm_time', models.DateTimeField(blank=True, null=True, verbose_name='确认时间')), + ('release_user', models.CharField(blank=True, max_length=20, null=True, verbose_name='发布人')), + ('release_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')), + ('comparison_user', models.CharField(blank=True, max_length=20, null=True, verbose_name='比价人')), + ('comparison_time', models.DateTimeField(blank=True, null=True, verbose_name='比价时间')), + ('win_supplier_code', models.CharField(blank=True, max_length=20, null=True, verbose_name='中标供应商代码')), + ('win_price', models.DecimalField(blank=True, decimal_places=4, max_digits=12, null=True, verbose_name='中标价格')), + ('approval_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='EIP审核单号')), + ('approval_status', models.IntegerField(blank=True, null=True, verbose_name='EIP审核结果')), + ('remark', models.CharField(blank=True, max_length=100, null=True, verbose_name='备注')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ('update_user', models.CharField(blank=True, db_column='UpdateUser', max_length=20, null=True, verbose_name='最后更新人')), + ('update_time', models.DateTimeField(blank=True, db_column='UpdateTime', null=True, verbose_name='最后更新时间')), + ], + options={ + 'verbose_name': '询价单', + 'verbose_name_plural': '询价单', + 'db_table': 'pis_proc_inquiry_master', + 'ordering': ('-create_time', '-id'), + }, + ), + migrations.CreateModel( + name='InquiryAttachment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('file_type', models.IntegerField(choices=[(1, '产品图纸'), (2, '招标文件'), (3, '其它文件')], db_column='file_type', verbose_name='文件类型')), + ('file_name', models.CharField(db_column='file_name', max_length=100, verbose_name='文件名称')), + ('file_path', models.CharField(db_column='file_path', max_length=200, verbose_name='文件路径')), + ('upload_time', models.DateTimeField(auto_now_add=True, db_column='uploadtime', verbose_name='上传时间')), + ('upload_user', models.CharField(blank=True, db_column='uploaduser', max_length=20, null=True, verbose_name='上传人')), + ], + options={ + 'verbose_name': '杂采询价单-附件关联表', + 'verbose_name_plural': '杂采询价单-附件关联表', + 'db_table': 'pis_proc_inquiry_attachment', + 'ordering': ('-upload_time',), + }, + ), + migrations.CreateModel( + name='InquiryMaterialCost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('material_spec', models.CharField(blank=True, db_column='material_spec', max_length=20, null=True, verbose_name='材料规格')), + ('length', models.CharField(blank=True, db_column='Length', max_length=10, null=True, verbose_name='长')), + ('width', models.CharField(blank=True, db_column='Width', max_length=10, null=True, verbose_name='宽')), + ('height', models.CharField(blank=True, db_column='Height', max_length=10, null=True, verbose_name='高')), + ('unit_price', models.DecimalField(blank=True, db_column='UnitPrice', decimal_places=2, max_digits=10, null=True, verbose_name='单价')), + ('qty', models.IntegerField(blank=True, db_column='qty', null=True, verbose_name='数量')), + ('specific_gravity', models.CharField(blank=True, db_column='SpecificGravity', max_length=10, null=True, verbose_name='比重')), + ('material_cost', models.DecimalField(blank=True, db_column='material_cost', decimal_places=4, max_digits=12, null=True, verbose_name='材料费用')), + ('remark', models.CharField(blank=True, db_column='remark', max_length=100, null=True, verbose_name='备注')), + ('option_json', models.TextField(blank=True, db_column='OptionJson', null=True, verbose_name='可选扩展信息')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采询价单-材料成本明细表', + 'verbose_name_plural': '杂采询价单-材料成本明细表', + 'db_table': 'pis_proc_inquiry_material_cost', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='InquiryOtherCost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('packaging_cost', models.DecimalField(blank=True, db_column='packaging_cost', decimal_places=4, max_digits=12, null=True, verbose_name='包装费用')), + ('transportation_cost', models.DecimalField(blank=True, db_column='transportation_cost', decimal_places=4, max_digits=12, null=True, verbose_name='运输费用')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采询价单-其它成本明细表', + 'verbose_name_plural': '杂采询价单-其它成本明细表', + 'db_table': 'pis_proc_inquiry_other_cost', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='InquiryProcessCost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('process_station', models.CharField(blank=True, db_column='process_station', max_length=20, null=True, verbose_name='加工工站')), + ('unit', models.CharField(blank=True, db_column='Unit', max_length=20, null=True, verbose_name='单位')), + ('unit_rate', models.DecimalField(blank=True, db_column='UnitRate', decimal_places=4, max_digits=12, null=True, verbose_name='费率')), + ('process_qty', models.CharField(blank=True, db_column='ProcessQty', max_length=20, null=True, verbose_name='加工数量')), + ('process_price', models.DecimalField(blank=True, db_column='ProcessPrice', decimal_places=2, max_digits=10, null=True, verbose_name='加工费用')), + ('remark', models.CharField(blank=True, db_column='remark', max_length=100, null=True, verbose_name='备注')), + ('option_json', models.TextField(blank=True, db_column='OptionJson', null=True, verbose_name='可选扩展信息')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采询价单-加工成本明细表', + 'verbose_name_plural': '杂采询价单-加工成本明细表', + 'db_table': 'pis_proc_inquiry_process_cost', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='InquiryProfitCost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('tax_rate', models.DecimalField(blank=True, db_column='tax_rate', decimal_places=4, max_digits=12, null=True, verbose_name='税率')), + ('profit_rate', models.DecimalField(blank=True, db_column='profit_rate', decimal_places=4, max_digits=12, null=True, verbose_name='利润率')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采询价单-税费利润明细表', + 'verbose_name_plural': '杂采询价单-税费利润明细表', + 'db_table': 'pis_proc_inquiry_profit_cost', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='InquiryRfqItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('product_name', models.CharField(blank=True, db_column='product_name', max_length=20, null=True, verbose_name='产品名称')), + ('unit', models.CharField(blank=True, db_column='unit', max_length=10, null=True, verbose_name='计量单位')), + ('qty', models.IntegerField(blank=True, db_column='qty', null=True, verbose_name='需求数量数量')), + ('is_bom', models.IntegerField(blank=True, db_column='IsBom', null=True, verbose_name='是否成本结构')), + ('unit_price', models.DecimalField(blank=True, db_column='unit_price', decimal_places=4, max_digits=12, null=True, verbose_name='标准品单价')), + ('total_material_cost', models.DecimalField(blank=True, db_column='total_material_cost', decimal_places=4, max_digits=12, null=True, verbose_name='材料成本合计')), + ('total_processing_cost', models.DecimalField(blank=True, db_column='total_processing_cost', decimal_places=4, max_digits=12, null=True, verbose_name='加工成本合计')), + ('total_other_expense', models.DecimalField(blank=True, db_column='total_other_expense', decimal_places=4, max_digits=12, null=True, verbose_name='其他费用合计')), + ('total_opex_amt', models.DecimalField(blank=True, db_column='total_opex_amt', decimal_places=4, max_digits=12, null=True, verbose_name='管销研费用')), + ('profit_rate', models.DecimalField(blank=True, db_column='profit_rate', decimal_places=4, max_digits=12, null=True, verbose_name='利润')), + ('total_price_excl_tax', models.DecimalField(blank=True, db_column='total_price_excl_tax', decimal_places=4, max_digits=12, null=True, verbose_name='不含税总价')), + ('tax_rate', models.DecimalField(blank=True, db_column='tax_rate', decimal_places=4, max_digits=12, null=True, verbose_name='税费')), + ('total_price_incl_tax', models.DecimalField(blank=True, db_column='total_price_incl_tax', decimal_places=4, max_digits=12, null=True, verbose_name='含税总价')), + ('option_json', models.TextField(blank=True, db_column='OptionJson', null=True, verbose_name='可选扩展信息')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='创建时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采询价单-上阶物料明细表', + 'verbose_name_plural': '杂采询价单-上阶物料明细表', + 'db_table': 'pis_misc_rfq_items', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='InquirySupplier', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('supplier_code', models.CharField(db_column='supplier_code', max_length=20, verbose_name='供应商代码')), + ('supplier_name', models.CharField(db_column='supplier_name', max_length=50, verbose_name='供应商名称')), + ('contact_person', models.CharField(blank=True, db_column='contact_person', max_length=20, null=True, verbose_name='联系人')), + ('contact_phone', models.CharField(blank=True, db_column='contact_phone', max_length=20, null=True, verbose_name='联系电话')), + ('contact_email', models.CharField(blank=True, db_column='contact_email', max_length=50, null=True, verbose_name='联系邮箱')), + ('create_time', models.DateTimeField(auto_now_add=True, db_column='createtime', verbose_name='添加时间')), + ('create_user', models.CharField(blank=True, db_column='createuser', max_length=20, null=True, verbose_name='添加人')), + ], + options={ + 'verbose_name': '杂采询价单-供应商关联表', + 'verbose_name_plural': '杂采询价单-供应商关联表', + 'db_table': 'pis_proc_inquiry_supplier', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='MiscProcMaterial', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(blank=True, help_text='交易厂区(公司)', max_length=10, null=True, verbose_name='交易厂区')), + ('partid', models.CharField(blank=True, help_text='料号(物料编码)', max_length=50, null=True, verbose_name='料号')), + ('partid_name', models.CharField(help_text='物料说明', max_length=50, verbose_name='物料说明')), + ('specification', models.CharField(help_text='品名规格', max_length=50, verbose_name='品名规格')), + ('unit', models.CharField(help_text='单位', max_length=50, verbose_name='单位')), + ('partid_category_id', models.IntegerField(blank=True, help_text='物料分类(1:模治具,2:石墨 3:)', null=True, verbose_name='物料分类')), + ('status', models.IntegerField(default=1, help_text='启用否(1:启用 0 禁用)', verbose_name='启用否')), + ], + options={ + 'verbose_name': '杂采料号信息', + 'verbose_name_plural': '杂采料号信息', + 'db_table': 'pis_misc_proc_materials', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='MiscProcurementMaterialInfo', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('factory', models.CharField(blank=True, help_text='交易厂区(公司)', max_length=10, null=True, verbose_name='交易厂区')), + ('materialtype', models.CharField(blank=True, help_text='材质', max_length=50, null=True, verbose_name='材质')), + ('density', models.CharField(help_text='比重', max_length=20, verbose_name='比重')), + ('price', models.DecimalField(decimal_places=2, help_text='单价', max_digits=5, verbose_name='单价')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ], + options={ + 'verbose_name': '杂采材料信息', + 'verbose_name_plural': '杂采材料信息', + 'db_table': 'pis_misc_procurement_material_info', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='MiscProcurementStationInfo', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('company_code', models.CharField(blank=True, help_text='交易厂区(公司)', max_length=10, null=True, verbose_name='交易厂区')), + ('stationname', models.CharField(help_text='工站名称', max_length=50, verbose_name='工站名称')), + ('stationcode', models.CharField(blank=True, help_text='工站代码', max_length=20, null=True, verbose_name='工站代码')), + ('stationtype', models.IntegerField(help_text='工站类型归属(1:模治具 2:石墨)', verbose_name='工站类型归属')), + ('unit', models.CharField(blank=True, help_text='计量单位', max_length=20, null=True, verbose_name='计量单位')), + ('rate', models.DecimalField(decimal_places=2, help_text='费率', max_digits=10, verbose_name='费率')), + ('status', models.IntegerField(default=1, help_text='可用状态(1可用0不可用)', verbose_name='可用状态')), + ], + options={ + 'verbose_name': '杂采加工工站信息', + 'verbose_name_plural': '杂采加工工站信息', + 'db_table': 'pis_misc_procurement_station_info', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='PriceTemplate', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('code', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='模板编号')), + ('name', models.CharField(max_length=128, verbose_name='模板名称')), + ('procurement_category', models.CharField(choices=[('strategic', 'strategic'), ('misc', 'misc')], db_index=True, max_length=32, verbose_name='采购类别')), + ('category', models.CharField(choices=[('tooling', 'tooling'), ('stamping', 'stamping'), ('injection', 'injection')], db_index=True, max_length=32, verbose_name='产品类别')), + ('remark', models.TextField(blank=True, null=True, verbose_name='备注')), + ('active', models.BooleanField(default=True, verbose_name='是否有效')), + ('enable_cost_structure', models.BooleanField(default=True, verbose_name='启用成本结构')), + ], + options={ + 'verbose_name': '价格模板', + 'verbose_name_plural': '价格模板', + 'db_table': 'pis_proc_price_template', + 'ordering': ('-update_datetime', '-id'), + }, + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0002_initial.py b/backend/apps/pisadmin/miscprocurement/migrations/0002_initial.py new file mode 100644 index 0000000..17b8b43 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0002_initial.py @@ -0,0 +1,111 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('miscprocurement', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='pricetemplate', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='miscprocurementstationinfo', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='miscprocurementmaterialinfo', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='miscprocmaterial', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='inquirysupplier', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='suppliers', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiryrfqitem', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='rfq_items', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiryprofitcost', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='profit_costs', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiryprocesscost', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='process_costs', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiryothercost', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='other_costs', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquirymaterialcost', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='material_costs', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiryattachment', + name='inquiry_no', + field=models.ForeignKey(db_column='inquiry_no', on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='miscprocurement.inquiry', to_field='inquiry_no', verbose_name='询价单号'), + ), + migrations.AddField( + model_name='inquiry', + name='creator', + field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'), + ), + migrations.AddField( + model_name='costestimatetemplatebody', + name='template_no', + field=models.ForeignKey(db_column='TemplateNo', on_delete=django.db.models.deletion.CASCADE, related_name='items', to='miscprocurement.costestimatetemplatehead', to_field='template_no', verbose_name='模板编号'), + ), + migrations.AlterUniqueTogether( + name='inquirysupplier', + unique_together={('inquiry_no', 'part_id', 'supplier_code')}, + ), + migrations.AddIndex( + model_name='inquiryrfqitem', + index=models.Index(fields=['part_id'], name='pis_misc_rf_Partid_63eb20_idx'), + ), + migrations.AddIndex( + model_name='inquiryprofitcost', + index=models.Index(fields=['part_id'], name='pis_proc_in_PartId_aaa0eb_idx'), + ), + migrations.AddIndex( + model_name='inquiryprocesscost', + index=models.Index(fields=['part_id'], name='pis_proc_in_PartId_65ad97_idx'), + ), + migrations.AddIndex( + model_name='inquiryothercost', + index=models.Index(fields=['part_id'], name='pis_proc_in_PartId_33e9d8_idx'), + ), + migrations.AddIndex( + model_name='inquirymaterialcost', + index=models.Index(fields=['part_id'], name='pis_proc_in_PartId_4bedee_idx'), + ), + migrations.AlterUniqueTogether( + name='inquiryattachment', + unique_together={('inquiry_no', 'part_id', 'file_name')}, + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py b/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py new file mode 100644 index 0000000..c0c2d63 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0003_remove_pricetemplate_creator_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 4.2.14 on 2026-03-24 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0002_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='pricetemplate', + name='creator', + ), + migrations.AddField( + model_name='costestimatetemplatebody', + name='version', + field=models.IntegerField(db_column='Version', default=1, verbose_name='版本号'), + ), + migrations.AddField( + model_name='costestimatetemplatehead', + name='status', + field=models.IntegerField(choices=[(0, '未确认'), (1, '已确认'), (2, '作废')], db_column='Status', default=0, verbose_name='状态'), + ), + migrations.AddField( + model_name='costestimatetemplatehead', + name='version', + field=models.IntegerField(db_column='Version', default=1, verbose_name='版本号'), + ), + migrations.AlterField( + model_name='costestimatetemplatebody', + name='template_no', + field=models.CharField(db_column='TemplateNo', max_length=20, verbose_name='模板编号'), + ), + migrations.AlterField( + model_name='costestimatetemplatehead', + name='template_no', + field=models.CharField(db_column='TemplateNo', db_index=True, max_length=20, verbose_name='模板编号'), + ), + migrations.AddIndex( + model_name='costestimatetemplatebody', + index=models.Index(fields=['template_no', 'version'], name='idx_cost_template_body_no_ver'), + ), + migrations.AddIndex( + model_name='costestimatetemplatehead', + index=models.Index(fields=['template_no', 'version'], name='idx_cost_template_head_no_ver'), + ), + migrations.AddConstraint( + model_name='costestimatetemplatehead', + constraint=models.UniqueConstraint(fields=('template_no', 'version'), name='uq_cost_template_head_no_ver'), + ), + migrations.DeleteModel( + name='PriceTemplate', + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py b/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py new file mode 100644 index 0000000..e330044 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0004_inquiry_template_version_inquirymaterialcost_weight.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.14 on 2026-03-26 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0003_remove_pricetemplate_creator_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='inquiry', + name='template_version', + field=models.IntegerField(db_column='TemplateVersion', default=1, help_text='与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。', verbose_name='模板版本号'), + preserve_default=False, + ), + migrations.AddField( + model_name='inquirymaterialcost', + name='weight', + field=models.DecimalField(blank=True, db_column='Weight', decimal_places=4, max_digits=12, null=True, verbose_name='重量'), + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py b/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py new file mode 100644 index 0000000..bebdc0c --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0005_alter_inquiry_template_version.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.14 on 2026-03-26 15:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0004_inquiry_template_version_inquirymaterialcost_weight'), + ] + + operations = [ + migrations.AlterField( + model_name='inquiry', + name='template_version', + field=models.IntegerField(db_column='TemplateVersion', default=1, help_text='与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。', verbose_name='模板版本号'), + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py b/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py new file mode 100644 index 0000000..f9b0b50 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more.py @@ -0,0 +1,140 @@ +# Generated by Django 4.2.14 on 2026-03-27 16:02 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('miscprocurement', '0005_alter_inquiry_template_version'), + ] + + operations = [ + migrations.AddField( + model_name='inquiry', + name='bid_end_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标截止时间'), + ), + migrations.AddField( + model_name='inquiry', + name='bid_start_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标开始时间'), + ), + migrations.AddField( + model_name='inquiry', + name='buying_method', + field=models.IntegerField(blank=True, choices=[(1, '询价'), (2, '招标')], null=True, verbose_name='采购方式(寻源方式)'), + ), + migrations.CreateModel( + name='RFQOperationLogs', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('operation_type', models.CharField(choices=[(1, '询价单创建'), (2, '询价单确认'), (3, '询价单发布'), (4, '询价单还原'), (5, '询价发送通知'), (6, '报价'), (7, '比议价'), (8, '议价审核提交'), (9, '议价审核完成'), (10, '议价审核驳回')], db_column='OperationType', max_length=20, verbose_name='操作类型')), + ('operation_user', models.CharField(blank=True, db_column='OperationUser', max_length=20, null=True, verbose_name='操作人')), + ('operation_time', models.DateTimeField(blank=True, db_column='OperationTime', null=True, verbose_name='操作时间')), + ('operation_desc', models.TextField(blank=True, db_column='OperationDesc', max_length=200, null=True, verbose_name='操作描述')), + ('inquiry_no', models.CharField(db_column='InquiryNo', db_index=True, max_length=20, verbose_name='询价单号')), + ('quotation_no', models.CharField(db_column='QuotationNo', db_index=True, max_length=20, verbose_name='报价单号')), + ('per_status', models.CharField(blank=True, db_column='PerStatus', max_length=50, null=True, verbose_name='作业前状态')), + ('cur_status', models.TextField(blank=True, db_column='CurStatus', max_length=100, null=True, verbose_name='作业后状态')), + ('is_show_user', models.IntegerField(blank=True, db_column='IsShowUser', help_text='履历显示否(1:是 0:否)', null=True, verbose_name='履历显示否')), + ('purchase_type', models.IntegerField(choices=[(1, '策采'), (2, '杂采')], db_column='PurchaseType', help_text='采购类别(1:策采 2:杂采)', verbose_name='采购类别')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '询价单操作日志', + 'verbose_name_plural': '询价单操作日志', + 'db_table': 'pis_rfq_operation_logs', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'quotation_no'], name='pis_rfq_ope_Inquiry_57e005_idx')], + }, + ), + migrations.CreateModel( + name='MiscNegotiationRecords', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('supplier_code', models.CharField(blank=True, db_column='SupplierCode', max_length=50, null=True, verbose_name='供应商代码')), + ('bargaining_price', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价后价格')), + ('bargaining_time', models.DateTimeField(blank=True, null=True, verbose_name='议价时间')), + ('bargaining_user', models.CharField(blank=True, db_column='BargainingUser', max_length=20, null=True, verbose_name='议价人')), + ('is_awarded', models.IntegerField(blank=True, default=0, help_text='是否中标(1:是 0:否)', null=True, verbose_name='是否中标')), + ('quotation_no', models.CharField(blank=True, db_column='QuotationNo', max_length=20, null=True, verbose_name='报价单号')), + ('total_price_excl_tax', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价前不含税总价')), + ('total_price_incl_tax', models.DecimalField(blank=True, decimal_places=4, default=0, max_digits=12, null=True, verbose_name='议价前含税总价')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '杂采议价记录表', + 'verbose_name_plural': '杂采议价记录表', + 'db_table': 'pis_misc_negotiation_records', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_ne_inquiry_5d842e_idx'), models.Index(fields=['inquiry_no', 'part_id', 'quotation_no'], name='pis_misc_ne_inq_part_qtn_idx')], + }, + ), + migrations.CreateModel( + name='MiscLowPriceHeader', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, help_text='业务主键列之一;逻辑主键与 id 并存', max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('souce_no', models.CharField(blank=True, db_column='SouceNo', max_length=20, null=True, verbose_name='来源单号(报价单&询价单)')), + ('cost_type', models.CharField(choices=[('1', '材料'), ('2', '加工'), ('3', '包装费'), ('4', '运输费'), ('5', '管销研费用'), ('6', '利润率')], db_column='CostType', max_length=50, verbose_name='成本类别')), + ('item_no', models.CharField(db_column='ItemNo', help_text='如:铝等项次名称', max_length=100, verbose_name='项次名')), + ('min_price', models.CharField(db_column='MinPrice', help_text='表结构为 varchar,若需参与运算可在业务层转换', max_length=10, verbose_name='最低价格')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '比价-制程最低价记录主表', + 'verbose_name_plural': '比价-制程最低价记录主表', + 'db_table': 'pis_misc_low_price_header', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_lo_inquiry_51eb98_idx')], + }, + ), + migrations.CreateModel( + name='MiscLowPriceDetail', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('inquiry_no', models.CharField(db_column='inquiry_no', db_index=True, max_length=20, verbose_name='询价单号')), + ('part_id', models.CharField(db_column='PartId', max_length=50, verbose_name='产品料号')), + ('cost_type', models.CharField(db_column='CostType', help_text='仅支持1-材料', max_length=50, verbose_name='成本类别')), + ('material_spec', models.CharField(db_column='materialspec', max_length=50, verbose_name='材料规格')), + ('item_no', models.CharField(choices=[(1, '重量'), (2, '单价')], db_column='ItemNo', help_text='策采:重量、损耗、单价;杂采:重量、单价', max_length=100, verbose_name='项次名')), + ('value', models.CharField(db_column='Value', max_length=10, verbose_name='最小值')), + ('souce_no', models.CharField(db_column='SouceNo', max_length=20, verbose_name='来源单号(报价单&询价单)')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '比价-制程最低价记录次表', + 'verbose_name_plural': '比价-制程最低价记录次表', + 'db_table': 'pis_misc_low_price_detail', + 'ordering': ('-create_datetime', 'id'), + 'indexes': [models.Index(fields=['inquiry_no', 'part_id'], name='pis_misc_lo_inquiry_005d6d_idx')], + }, + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py b/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py new file mode 100644 index 0000000..de4eac2 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/0007_alter_misclowpriceheader_item_no.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.14 on 2026-03-29 19:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('miscprocurement', '0006_inquiry_bid_end_time_inquiry_bid_start_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='misclowpriceheader', + name='item_no', + field=models.CharField(db_column='ItemNo', help_text='根据成本类别获取询价单对应子表数据行,材料类别 - 材料成本的产品料号 part_id, 加工类别 - 加工工站 process_station', max_length=100, verbose_name='项次名'), + ), + ] diff --git a/backend/apps/pisadmin/miscprocurement/migrations/__init__.py b/backend/apps/pisadmin/miscprocurement/migrations/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/migrations/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/apps/pisadmin/miscprocurement/models.py b/backend/apps/pisadmin/miscprocurement/models.py new file mode 100644 index 0000000..2c1a01c --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/models.py @@ -0,0 +1,971 @@ +import logging +from typing import Iterable, Optional + +from django.db import models +from django.utils import timezone + +from dvadmin.utils.models import CoreModel, table_prefix + +logger = logging.getLogger(__name__) + + +class MiscProcurementMaterialInfo(CoreModel): + """杂采材料信息""" + + factory = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True) + materialtype = models.CharField(max_length=50, verbose_name="材质", help_text="材质", null=True, blank=True) + density = models.CharField(max_length=20, verbose_name="比重", help_text="比重") + price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="单价", help_text="单价") + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + + class Meta: + db_table = table_prefix + "misc_procurement_material_info" + verbose_name = "杂采材料信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.materialtype or "" + + +class MiscProcurementStationInfo(CoreModel): + """杂采工站信息""" + + company_code = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True) + stationname = models.CharField(max_length=50, verbose_name="工站名称", help_text="工站名称") + stationcode = models.CharField(max_length=20, verbose_name="工站代码", help_text="工站代码", null=True, blank=True) + stationtype = models.IntegerField(verbose_name="工站类型归属", help_text="工站类型归属(1:模治具 2:石墨)") + unit = models.CharField(max_length=20, verbose_name="计量单位", help_text="计量单位", null=True, blank=True) + rate = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="费率", help_text="费率") + status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)") + + class Meta: + db_table = table_prefix + "misc_procurement_station_info" + verbose_name = "杂采加工工站信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.stationname + + +class MiscProcMaterial(CoreModel): + """杂采料号信息""" + + company_code = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True) + partid = models.CharField(max_length=50, verbose_name="料号", help_text="料号(物料编码)", null=True, blank=True) + partid_name = models.CharField(max_length=50, verbose_name="物料说明", help_text="物料说明") + specification = models.CharField(max_length=50, verbose_name="品名规格", help_text="品名规格") + unit = models.CharField(max_length=50, verbose_name="单位", help_text="单位") + partid_category_id = models.IntegerField(verbose_name="物料分类", help_text="物料分类(1:模治具,2:石墨 3:)", null=True, blank=True) + status = models.IntegerField(default=1, verbose_name="启用否", help_text="启用否(1:启用 0 禁用)") + + class Meta: + db_table = table_prefix + "misc_proc_materials" + verbose_name = "杂采料号信息" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + def __str__(self): + return self.partid or self.partid_name + + +class CostEstimateTemplateHead(models.Model): + """成本估算模板主表;确认某一版本时,同 template_no 下更低版本的主表行由接口批量改为作废(2)。""" + STATUS_CHOICES = ( + (0, "未确认"), + (1, "已确认"), + (2, "作废"), + ) + + template_no = models.CharField(max_length=20, db_index=True, db_column="TemplateNo", verbose_name="模板编号") + template_name = models.CharField(max_length=50, db_column="TemplateName", verbose_name="模板名称") + procurement_category = models.CharField(max_length=4, db_column="ProcurementCategory", verbose_name="采购类别:1-策采;2-杂采") + is_bom = models.CharField(max_length=4, db_column="IsBom", verbose_name="启用BOM否") + acti = models.CharField(max_length=4, db_column="Acti", null=True, blank=True, default="Y", verbose_name="有效否:Y/N") + # 允许为空:前端备注可不填 + template_desc = models.CharField(max_length=500, db_column="TemplateDesc", null=True, blank=True, verbose_name="模板描述") + is_can_add_materials = models.IntegerField(db_column="IsCanAddMaterials", default=0, verbose_name="材料允许供应商增行") + is_can_add_process = models.IntegerField(db_column="IsCanAddprocess", default=0, verbose_name="加工允许供应商增行") + create_user = models.CharField(max_length=20, db_column="CreateUser", null=True, blank=True, verbose_name="创建人") + create_time = models.DateTimeField(db_column="CreateTime", null=True, blank=True, verbose_name="创建时间") + update_user = models.CharField(max_length=20, db_column="UpdateUser", null=True, blank=True, verbose_name="最后更新人") + update_time = models.DateTimeField(db_column="UpdateTime", null=True, blank=True, verbose_name="最后更新时间") + version = models.IntegerField(db_column="Version", default=1, verbose_name="版本号") + status = models.IntegerField(db_column="Status", default=0, verbose_name="状态", choices=STATUS_CHOICES) + + class Meta: + db_table = "t_CostEstimate_Template_Head" + verbose_name = "成本估算模板主表" + verbose_name_plural = verbose_name + ordering = ("-update_time", "-id") + indexes = [ + models.Index(fields=["template_no", "version"], name="idx_cost_template_head_no_ver"), + ] + constraints = [ + models.UniqueConstraint( + fields=("template_no", "version"), + name="uq_cost_template_head_no_ver", + ), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.template_name + + @property + def items(self): + """明细表按 (template_no, version) 与当前主表行对齐(非 ORM 反向关联)。""" + return CostEstimateTemplateBody.objects.filter( + template_no=self.template_no, + version=self.version, + ).order_by("item_order", "id") + + +class CostEstimateTemplateBody(models.Model): + COST_CATEGORY_CHOICES = ( + ("1", "材料成本"), + ("2", "加工成本"), + ("3", "其它成本"), + ("4", "管销研成本"), + ("5", "利润成本"), + ("6", "税费成本"), + ("7", "产品明细"), + ) + + SUPPLIER_REQUIRED_CHOICES = ( + (0, " "), + (1, "带出不可修改"), + (2, "带出可修改"), + (3, "不带出必填"), + (4, "不带出可空"), + (5, "必填"), + (6, "可空"), + ) + + # 与主表通过 (template_no, version) 逻辑关联;主表上该组合唯一(见 CostEstimateTemplateHead.Meta.constraints) + template_no = models.CharField(max_length=20, db_column="TemplateNo", verbose_name="模板编号") + cost_category = models.CharField(max_length=4, choices=COST_CATEGORY_CHOICES, db_column="CostCategory", verbose_name="成本类别") + item_order = models.IntegerField(db_column="ItemOrder", verbose_name="排序序号") + item_no = models.CharField(max_length=100, db_column="ItemNo", verbose_name="项次编号") + item_name_cn = models.CharField(max_length=100, db_column="ItemName_cn", verbose_name="中文名称") + item_name_en = models.CharField(max_length=100, db_column="ItemName_en", null=True, blank=True, verbose_name="英文名称") + item_name_vn = models.CharField(max_length=100, db_column="ItemName_vn", null=True, blank=True, verbose_name="越南语名称") + is_fixed = models.IntegerField(db_column="is_fixed", verbose_name="是否固定字段:1是0否") + is_computed = models.IntegerField(db_column="is_computed", verbose_name="是否系统自动计算/带出") + purchaser_required = models.IntegerField(db_column="PurchaserRequired", verbose_name="采购必填否(1/0)") + supplier_required = models.IntegerField(db_column="SupplierRequired", choices=SUPPLIER_REQUIRED_CHOICES, verbose_name="供应商操作") + remark = models.CharField(max_length=200, db_column="Remark", null=True, blank=True, verbose_name="备注") + create_user = models.CharField(max_length=20, db_column="CreateUser", null=True, blank=True, verbose_name="创建人") + create_time = models.DateTimeField(db_column="CreateTime", null=True, blank=True, verbose_name="创建时间") + update_user = models.CharField(max_length=20, db_column="UpdateUser", null=True, blank=True, verbose_name="最后更新人") + update_time = models.DateTimeField(db_column="UpdateTime", null=True, blank=True, verbose_name="最后更新时间") + version = models.IntegerField(db_column="Version", default=1, verbose_name="版本号") + + class Meta: + db_table = "t_CostEstimate_Template_Body" + verbose_name = "成本估算模板明细" + verbose_name_plural = verbose_name + ordering = ("item_order", "id") + indexes = [ + models.Index(fields=["template_no", "version"], name="idx_cost_template_body_no_ver"), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.item_no + + +class Inquiry(CoreModel): + """杂采询价单主表 + + 与供应商端报价联动:当全部报价单都不再处于待报价/报价中时,「发布」「报价中」可经 + `Inquiry.sync_to_quote_closed_when_no_open_quotations` 收口为「报价结束」; + 当供应商名单中每家均有一条「已报价」报价单时,可经 `Inquiry.sync_to_quote_closed_when_all_suppliers_quoted` 收口(见 `submit`)。 + """ + + # (1开立;2确认;3发布;4报价中;5报价结束;6比议价中;7价格审核;8核价通过(结束);9落标(结束);0作废) + STATUS_CHOICES = ( + (1, "开立"), + (2, "确认"), + (3, "发布"), + (4, "报价中"), + (5, "报价结束"), + (6, "比议价中"), + (7, "价格审核"), + (8, "核价通过(结束)"), + (9, "落标(结束)"), + (0, "作废"), + ) + # 采购端仪表盘「我的待办任务」:发布、报价中、报价结束(业务上常称「报价完成」)、比议价中 + BUYER_DASHBOARD_TASK_STATUSES = (3, 4, 5, 6) + PAYMENT_METHOD_CHOICES = ( + (1, "月结30天"), + (2, "月结60天"), + (3, "不到付款"), + (4, "预付30%"), + (5, "预付50%"), + (6, "余款至生产"), + (7, "价格审核"), + (8, "价格结算(运费)"), + (9, "新建(结束)"), + ) + PURCHASE_TYPE_CHOICES = ( + (1, "策采"), + (2, "杂采"), + ) + CATEGORY_CHOICES = ( + (1, "中压"), + (2, "活塞"), + (3, "模具/夹具"), + (4, "管"), + ) + BUYING_METHOD_CHOICES = ( + (1, "询价"), + (2, "招标"), + ) + + inquiry_no = models.CharField(max_length=20, unique=True, db_index=True, verbose_name="询价单号") + title = models.CharField(max_length=20, verbose_name="询价单名称") + purchase_type = models.IntegerField(choices=PURCHASE_TYPE_CHOICES, verbose_name="采购类别") + material_type = models.CharField(max_length=20, null=True, blank=True, verbose_name="材料类型") + template = models.CharField( + max_length=20, + verbose_name="询价模版", + help_text="对应成本估算模板编号;发布生成供应商报价单时,子表字段是否从询价单带入由该模板明细 is_computed=1 或 supplier_required 为 1/2 决定。", + ) + template_version = models.IntegerField( + db_column="TemplateVersion", + default=1, + verbose_name="模板版本号", + help_text="与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。", + ) + is_bom = models.IntegerField(default=0, verbose_name="是否BOM否") + currency = models.CharField(max_length=20, default="CNY", verbose_name="交易币别") + company_code = models.CharField(max_length=20, null=True, blank=True, verbose_name="公司代码") + purchase_dept = models.CharField(max_length=20, null=True, blank=True, verbose_name="采购部门") + buyer = models.CharField(max_length=20, verbose_name="采购负责人") + quote_deadline = models.DateTimeField(null=True, blank=True, verbose_name="报价截止时间") + target_price = models.DecimalField(db_column="target_price", max_digits=12, decimal_places=4, default=0, verbose_name="目标价格") + lead_time_days = models.IntegerField(default=0, verbose_name="交货周期(天)") + payment_method = models.IntegerField(choices=PAYMENT_METHOD_CHOICES, verbose_name="付款方式") + status = models.IntegerField(choices=STATUS_CHOICES, default=1, db_index=True, verbose_name="状态") + confirm_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="确认人") + confirm_time = models.DateTimeField(null=True, blank=True, verbose_name="确认时间") + release_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="发布人") + release_time = models.DateTimeField(null=True, blank=True, verbose_name="发布时间") + comparison_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="比价人") + comparison_time = models.DateTimeField(null=True, blank=True, verbose_name="比价时间") + win_supplier_code = models.CharField(max_length=20, null=True, blank=True, verbose_name="中标供应商代码") + win_price = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, verbose_name="中标价格") + approval_number = models.CharField(max_length=20, null=True, blank=True, verbose_name="EIP审核单号") + approval_status = models.IntegerField(null=True, blank=True, verbose_name="EIP审核结果") + remark = models.CharField(max_length=100, null=True, blank=True, verbose_name="备注") + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + update_user = models.CharField(max_length=20, db_column="UpdateUser", null=True, blank=True, verbose_name="最后更新人") + update_time = models.DateTimeField(db_column="UpdateTime", null=True, blank=True, verbose_name="最后更新时间") + buying_method = models.IntegerField(choices=BUYING_METHOD_CHOICES, null=True, blank=True, verbose_name="采购方式(寻源方式)") + bid_start_time = models.DateTimeField(null=True, blank=True, verbose_name="投标开始时间") + bid_end_time = models.DateTimeField(null=True, blank=True, verbose_name="投标截止时间") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_master" + verbose_name = "询价单" + verbose_name_plural = verbose_name + ordering = ("-create_time", "-id") + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.inquiry_no + + @classmethod + def sync_to_quote_closed_when_no_open_quotations( + cls, + inquiry_nos: Iterable[str], + *, + actor_username: Optional[str] = None, + ) -> int: + """ + 当某询价单下已不存在「待报价」(1) 或「报价中」(2) 的报价单时,若询价单仍为「发布」(3) 或「报价中」(4), + 则置为「报价结束」(5),并写操作日志。 + + 典型调用:供应商报价单因超过截止时间被批量置为「已过期」后,判断询价维度是否可收口为报价结束。 + """ + from apps.pissupplier.models import QuotationMaster # 避免 apps 间循环 import + + now = timezone.now() + username = (str(actor_username).strip()[:20] if actor_username else None) or None + unique = {str(x).strip() for x in inquiry_nos if x and str(x).strip()} + n = 0 + for inq_no in unique: + if QuotationMaster.objects.filter(inquiry_no=inq_no, status__in=(1, 2)).exists(): + continue + inq = cls.objects.filter(inquiry_no=inq_no, status__in=(3, 4)).first() + if not inq: + continue + old_status = int(inq.status if inq.status is not None else 0) + inq.status = 5 + inq.update_time = now + inq.update_datetime = now + if username: + inq.update_user = username + inq.save(update_fields=["status", "update_time", "update_user", "update_datetime"]) + RFQOperationLogs.try_append( + inquiry_no=inq.inquiry_no, + purchase_type=int(inq.purchase_type), + operation_type=6, + operation_user=username, + quotation_no="-", + per_status=old_status, + cur_status=5, + operation_desc="超过报价截止时间,询价单截止报价", + ) + n += 1 + return n + + @classmethod + def sync_to_quote_closed_when_all_suppliers_quoted( + cls, + inquiry_no: str, + *, + actor_username: Optional[str] = None, + quotation_no: Optional[str] = None, + ) -> bool: + """ + 若询价单供应商名单(InquirySupplier)中每个供应商在 `QuotationMaster` 上均有一条「已报价」(3) 记录, + 且询价单仍为「发布」(3) 或「报价中」(4),则将询价单置为「报价结束」(5)。 + + 用于最后一户提交正式报价后收口,不依赖是否已超过报价截止时间。 + """ + from apps.pissupplier.models import QuotationMaster # 避免 apps 间循环 import + + inq_no = (inquiry_no or "").strip() + if not inq_no: + return False + inq = cls.objects.filter(inquiry_no=inq_no, status__in=(3, 4)).first() + if not inq: + return False + + raw_codes = ( + InquirySupplier.objects.filter(inquiry_no=inq_no) + .values_list("supplier_code", flat=True) + .distinct() + ) + supplier_codes = {str(c).strip() for c in raw_codes if c and str(c).strip()} + if not supplier_codes: + return False + + for code in supplier_codes: + qm = QuotationMaster.objects.filter(inquiry_no=inq_no, supplier_code=code).first() + st = int(qm.status) if qm is not None and qm.status is not None else None + if st != 3: + return False + + old_status = int(inq.status if inq.status is not None else 0) + now = timezone.now() + username = (str(actor_username).strip()[:20] if actor_username else None) or None + inq.status = 5 + inq.update_time = now + inq.update_datetime = now + if username: + inq.update_user = username + inq.save(update_fields=["status", "update_time", "update_user", "update_datetime"]) + qn = (quotation_no or "-").strip()[:20] or "-" + qm_submit = ( + QuotationMaster.objects.filter(inquiry_no=inq_no, quotation_no=qn).first() + if qn != "-" + else None + ) + submitter_name = (getattr(qm_submit, "supplier_name", None) or "").strip() or "—" + try: + from apps.pisadmin.basicinfo.views.email_utils import send_quote_ended_notice_to_purchaser + + send_quote_ended_notice_to_purchaser(inq, last_quotation_no=qn) + except Exception: + logger.exception("报价结束通知邮件发送失败") + return True + + +class InquirySupplier(models.Model): + """杂采询价单-供应商关联表""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="suppliers", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + supplier_code = models.CharField(max_length=20, db_column="supplier_code", verbose_name="供应商代码") + supplier_name = models.CharField(max_length=50, db_column="supplier_name", verbose_name="供应商名称") + contact_person = models.CharField(max_length=20, db_column="contact_person", null=True, blank=True, verbose_name="联系人") + contact_phone = models.CharField(max_length=20, db_column="contact_phone", null=True, blank=True, verbose_name="联系电话") + contact_email = models.CharField(max_length=50, db_column="contact_email", null=True, blank=True, verbose_name="联系邮箱") + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="添加时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="添加人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_supplier" + verbose_name = "杂采询价单-供应商关联表" + verbose_name_plural = verbose_name + unique_together = ("inquiry_no", "part_id", "supplier_code") + ordering = ("id",) + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.supplier_name + + +class InquiryAttachment(models.Model): + """杂采询价单-附件关联表(采购方上传)。发布询价生成报价单时不写入 `pissupplier.QuotationAttachment`,供应商通过详情中的询价附件只读展示。""" + + FILE_TYPE_CHOICES = ( + (1, "产品图纸"), + (2, "招标文件"), + (3, "其它文件"), + ) + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="attachments", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + file_type = models.IntegerField(choices=FILE_TYPE_CHOICES, db_column="file_type", verbose_name="文件类型") + file_name = models.CharField(max_length=100, db_column="file_name", verbose_name="文件名称") + file_path = models.CharField(max_length=200, db_column="file_path", verbose_name="文件路径") + upload_time = models.DateTimeField(db_column="uploadtime", auto_now_add=True, verbose_name="上传时间") + upload_user = models.CharField(max_length=20, db_column="uploaduser", null=True, blank=True, verbose_name="上传人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_attachment" + verbose_name = "杂采询价单-附件关联表" + verbose_name_plural = verbose_name + unique_together = ("inquiry_no", "part_id", "file_name") + ordering = ("-upload_time",) + + def __str__(self) -> str: # pragma: no cover - simple repr + return self.file_name + + +class InquiryMaterialCost(models.Model): + """杂采询价单-材料成本明细表""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="material_costs", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + material_spec = models.CharField(max_length=20, db_column="material_spec", null=True, blank=True, verbose_name="材料规格") + length = models.CharField(max_length=10, db_column="Length", null=True, blank=True, verbose_name="长") + width = models.CharField(max_length=10, db_column="Width", null=True, blank=True, verbose_name="宽") + height = models.CharField(max_length=10, db_column="Height", null=True, blank=True, verbose_name="高") + unit_price = models.DecimalField(max_digits=10, decimal_places=2, db_column="UnitPrice", null=True, blank=True, verbose_name="单价") + qty = models.IntegerField(db_column="qty", null=True, blank=True, verbose_name="数量") + specific_gravity = models.CharField( + max_length=10, db_column="SpecificGravity", null=True, blank=True, verbose_name="比重" + ) + material_cost = models.DecimalField( + max_digits=12, decimal_places=4, db_column="material_cost", null=True, blank=True, verbose_name="材料费用" + ) + weight = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="Weight", + null=True, + blank=True, + verbose_name="重量", + ) + remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注") + option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息") + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_material_cost" + verbose_name = "杂采询价单-材料成本明细表" + verbose_name_plural = verbose_name + ordering = ("id",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return f"{self.inquiry_no_id}-{self.part_id}" + + +class InquiryProcessCost(models.Model): + """杂采询价单-加工成本明细表""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="process_costs", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + process_station = models.CharField( + max_length=20, db_column="process_station", null=True, blank=True, verbose_name="加工工站" + ) + # 单位为文本(如 PCS/小时/次),历史误建为 DecimalField 会导致前端传字符串时报校验错误 + unit = models.CharField(max_length=20, db_column="Unit", null=True, blank=True, verbose_name="单位") + unit_rate = models.DecimalField( + max_digits=12, decimal_places=4, db_column="UnitRate", null=True, blank=True, verbose_name="费率" + ) + process_qty = models.CharField(max_length=20, db_column="ProcessQty", null=True, blank=True, verbose_name="加工数量") + process_price = models.DecimalField( + max_digits=10, decimal_places=2, db_column="ProcessPrice", null=True, blank=True, verbose_name="加工费用" + ) + remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注") + option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息") + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_process_cost" + verbose_name = "杂采询价单-加工成本明细表" + verbose_name_plural = verbose_name + ordering = ("id",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return f"{self.inquiry_no_id}-{self.part_id}" + + +class InquiryOtherCost(models.Model): + """杂采询价单-其它成本明细表""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="other_costs", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + packaging_cost = models.DecimalField( + max_digits=12, decimal_places=4, db_column="packaging_cost", null=True, blank=True, verbose_name="包装费用" + ) + transportation_cost = models.DecimalField( + max_digits=12, decimal_places=4, db_column="transportation_cost", null=True, blank=True, verbose_name="运输费用" + ) + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_other_cost" + verbose_name = "杂采询价单-其它成本明细表" + verbose_name_plural = verbose_name + ordering = ("id",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return f"{self.inquiry_no_id}-{self.part_id}" + + +class InquiryProfitCost(models.Model): + """杂采询价单-税费利润明细表""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="profit_costs", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + tax_rate = models.DecimalField( + max_digits=12, decimal_places=4, db_column="tax_rate", null=True, blank=True, verbose_name="税率" + ) + profit_rate = models.DecimalField( + max_digits=12, decimal_places=4, db_column="profit_rate", null=True, blank=True, verbose_name="利润率" + ) + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + + class Meta: + db_table = table_prefix + "misc_proc_inquiry_profit_cost" + verbose_name = "杂采询价单-税费利润明细表" + verbose_name_plural = verbose_name + ordering = ("id",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return f"{self.inquiry_no_id}-{self.part_id}" + + +class InquiryRfqItem(models.Model): + """杂采询价单-上阶物料明细表(采购产品明细)""" + + inquiry_no = models.ForeignKey( + Inquiry, + to_field="inquiry_no", + db_column="inquiry_no", + on_delete=models.CASCADE, + related_name="rfq_items", + verbose_name="询价单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + product_name = models.CharField(max_length=20, db_column="product_name", null=True, blank=True, verbose_name="产品名称") + unit = models.CharField(max_length=10, db_column="unit", null=True, blank=True, verbose_name="计量单位") + qty = models.IntegerField(db_column="qty", null=True, blank=True, verbose_name="需求数量数量") + is_bom = models.IntegerField(db_column="IsBom", null=True, blank=True, verbose_name="是否成本结构") + unit_price = models.DecimalField( + max_digits=12, decimal_places=4, db_column="unit_price", null=True, blank=True, verbose_name="标准品单价" + ) + total_material_cost = models.DecimalField( + max_digits=12, decimal_places=4, db_column="total_material_cost", null=True, blank=True, verbose_name="材料成本合计" + ) + total_processing_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_processing_cost", + null=True, + blank=True, + verbose_name="加工成本合计", + ) + total_other_expense = models.DecimalField( + max_digits=12, decimal_places=4, db_column="total_other_expense", null=True, blank=True, verbose_name="其他费用合计" + ) + total_opex_amt = models.DecimalField( + max_digits=12, decimal_places=4, db_column="total_opex_amt", null=True, blank=True, verbose_name="管销研费用" + ) + profit_rate = models.DecimalField( + max_digits=12, decimal_places=4, db_column="profit_rate", null=True, blank=True, verbose_name="利润" + ) + total_price_excl_tax = models.DecimalField( + max_digits=12, decimal_places=4, db_column="total_price_excl_tax", null=True, blank=True, verbose_name="不含税总价" + ) + tax_rate = models.DecimalField( + max_digits=12, decimal_places=4, db_column="tax_rate", null=True, blank=True, verbose_name="税费" + ) + total_price_incl_tax = models.DecimalField( + max_digits=12, decimal_places=4, db_column="total_price_incl_tax", null=True, blank=True, verbose_name="含税总价" + ) + option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息") + create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间") + create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人") + + class Meta: + db_table = table_prefix + "misc_rfq_items" + verbose_name = "杂采询价单-上阶物料明细表" + verbose_name_plural = verbose_name + ordering = ("id",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self) -> str: # pragma: no cover - simple repr + return f"{self.inquiry_no_id}-{self.part_id}" + + +class MiscLowPriceHeader(CoreModel): + """比价-制程最低价记录主表。按询价单上阶物料料号分套;材料一行(最低重量×最低单价×数量)、加工一行(各报价单加工费合计之最小值);包装费/运输费/利润率各一行。""" + + # 成本类别: 1-材料; 2-加工; 3-包装费; 4-运输费; 5-管销研费用(杂采制程最低价不落库); 6-利润率 + COST_TYPE_CHOICES = ( + ("1", "材料"), + ("2", "加工"), + ("3", "包装费"), + ("4", "运输费"), + ("5", "管销研费用"), + ("6", "利润率"), + ) + + inquiry_no = models.CharField( + max_length=20, + db_index=True, + db_column="inquiry_no", + verbose_name="询价单号", + help_text="业务主键列之一;逻辑主键与 id 并存", + ) + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + # 来源单号(报价单号/厂区等);材料行可聚合多来源(如 W:单号;U:单号);设计库字段名为 SouceNo + souce_no = models.CharField( + max_length=200, + null=True, + blank=True, + db_column="SouceNo", + verbose_name="来源单号(报价单&询价单)", + ) + cost_type = models.CharField( + max_length=50, + choices=COST_TYPE_CHOICES, + db_column="CostType", + verbose_name="成本类别", + ) + item_no = models.CharField( + max_length=100, + db_column="ItemNo", + verbose_name="项次名", + help_text="根据成本类别获取询价单对应子表数据行,材料类别 - 材料成本的产品料号 part_id, 加工类别 - 加工工站 process_station", + ) + min_price = models.CharField( + max_length=10, + db_column="MinPrice", + verbose_name="最低价格", + help_text="表结构为 varchar,若需参与运算可在业务层转换", + ) + + class Meta: + db_table = table_prefix + "misc_low_price_header" + verbose_name = "比价-制程最低价记录主表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["inquiry_no", "part_id"]), + ] + + def __str__(self) -> str: + return f"{self.inquiry_no}-{self.part_id}-{self.cost_type}" + + +class MiscLowPriceDetail(CoreModel): + """比价-制程最低价记录次表(材料:全供应商最低重量、最低单价各一行,material_spec 占位「-」)""" + + ITEM_NO_CHOICES = ( + ("1", "重量"), + ("2", "单价"), + ) + inquiry_no = models.CharField(max_length=20, db_column="inquiry_no", db_index=True, verbose_name="询价单号") + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + cost_type = models.CharField( + max_length=50, + db_column="CostType", + verbose_name="成本类别", + help_text="仅支持1-材料", + ) + material_spec = models.CharField(max_length=50, db_column="materialspec", verbose_name="材料规格") + item_no = models.CharField( + max_length=100, + db_column="ItemNo", + verbose_name="项次名", + help_text="策采:重量、损耗、单价;杂采:重量、单价", + choices=ITEM_NO_CHOICES, + ) + value = models.CharField(max_length=10, db_column="Value", verbose_name="最小值") + souce_no = models.CharField( + max_length=200, + db_column="SouceNo", + verbose_name="来源单号(报价单号或交易厂区)", + help_text="重量/单价取最小值时对应报价单单号;单价来自杂采材料信息时存交易厂区代码", + ) + + class Meta: + db_table = table_prefix + "misc_low_price_detail" + verbose_name = "比价-制程最低价记录次表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["inquiry_no", "part_id"]), + ] + + def __str__(self) -> str: + return f"{self.inquiry_no}-{self.part_id}-{self.item_no}" + + +class MiscNegotiationRecords(CoreModel): + """杂采议价记录表""" + + inquiry_no = models.CharField(max_length=20, db_column="inquiry_no", db_index=True, verbose_name="询价单号") + part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号") + supplier_code = models.CharField(max_length=50, null=True, blank=True, db_column="SupplierCode", verbose_name="供应商代码") + bargaining_price = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价后价格") + bargaining_time = models.DateTimeField(null=True, blank=True, verbose_name="议价时间") + bargaining_user = models.CharField(max_length=20, null=True, blank=True, db_column="BargainingUser", verbose_name="议价人") + is_awarded = models.IntegerField(default=0, null=True, blank=True, verbose_name="是否中标", help_text="是否中标(1:是 0:否)") + quotation_no = models.CharField(max_length=20, null=True, blank=True, db_column="QuotationNo", verbose_name="报价单号") + total_price_excl_tax = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价前不含税总价") + total_price_incl_tax = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价前含税总价") + + class Meta: + db_table = table_prefix + "misc_negotiation_records" + verbose_name = "杂采议价记录表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["inquiry_no", "part_id"]), + models.Index( + fields=["inquiry_no", "part_id", "quotation_no"], + name="pis_misc_ne_inq_part_qtn_idx", + ), + ] + + def __str__(self) -> str: + return f"{self.inquiry_no}-{self.part_id}-{self.quotation_no or self.supplier_code}" + + +class RFQOperationLogs(CoreModel): + """询价单操作日志表。 + + 按询价单号拉取全部记录:采购端 ``InquiryViewSet.operation_logs``( + ``GET .../inquiry/{pk}/operation_logs/``)。 + """ + + # (01询价单创;02询价单确认;03询价单发布;04询价单还原;05询价发送通知;06报价;07比议价;08议价审核提交;09议价审核完成;10议价审核驳回) + OPERATION_TYPE_CHOICES = ( + (1, "询价单创建"), + (2, "询价单确认"), + (3, "询价单发布"), + (4, "询价单还原"), + (5, "报价截止"), + (6, "供应商报价"), + (7, "比议价"), + (8, "议价审核提交"), + (9, "议价审核完成"), + (10, "议价审核驳回"), + ) + PURCHASE_TYPE_CHOICES = ( + (1, "策采"), + (2, "杂采"), + ) + + operation_type = models.CharField(max_length=20, db_column="OperationType", verbose_name="操作类型", choices=OPERATION_TYPE_CHOICES) + operation_user = models.CharField(max_length=20, null=True, blank=True, db_column="OperationUser", verbose_name="操作人") + operation_time = models.DateTimeField(null=True, blank=True, db_column="OperationTime", verbose_name="操作时间") + operation_desc = models.TextField(max_length=200, null=True, blank=True, db_column="OperationDesc", verbose_name="操作描述") + inquiry_no = models.CharField(max_length=20, db_column="InquiryNo", db_index=True, verbose_name="询价单号") + quotation_no = models.CharField(max_length=20, null=False, blank=False, db_column="QuotationNo", db_index=True, verbose_name="报价单号") + per_status = models.CharField(max_length=50, null=True, blank=True, db_column="PerStatus", verbose_name="作业前状态") + cur_status = models.TextField(max_length=100, null=True, blank=True, db_column="CurStatus", verbose_name="作业后状态") + is_show_user = models.IntegerField(null=True, blank=True, db_column="IsShowUser", verbose_name="履历显示否", help_text="履历显示否(1:是 0:否)") + purchase_type = models.IntegerField(choices=PURCHASE_TYPE_CHOICES, null=False, blank=False, db_column="PurchaseType", verbose_name="采购类别", help_text="采购类别(1:策采 2:杂采)") + + class Meta: + db_table = table_prefix + "rfq_operation_logs" + verbose_name = "询价单操作日志" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["inquiry_no", "quotation_no"]), + ] + + @classmethod + def inquiry_status_display(cls, code: Optional[int]) -> str: + """询价单状态码 → 与 Inquiry.STATUS_CHOICES 一致的可读文案。""" + if code is None: + return "" + try: + c = int(code) + except (TypeError, ValueError): + return str(code) + return dict(Inquiry.STATUS_CHOICES).get(c, str(c)) + + @classmethod + def append( + cls, + *, + inquiry_no: str, + purchase_type: int, + operation_type: int, + operation_user: Optional[str] = None, + quotation_no: Optional[str] = None, + per_status: Optional[int] = None, + cur_status: Optional[int] = None, + operation_desc: Optional[str] = None, + is_show_user: int = 1, + ) -> None: + """ + 写入一条询价操作日志。quotation_no 无关联报价单时使用 \"-\"。 + operation_type 与 OPERATION_TYPE_CHOICES 取值 1–10 一致。 + """ + qn = (quotation_no or "-").strip()[:20] or "-" + op_user = (operation_user or "").strip()[:20] if operation_user else None + now = timezone.now() + per_str = cls.inquiry_status_display(per_status) if per_status is not None else None + cur_str = cls.inquiry_status_display(cur_status) if cur_status is not None else None + desc = (operation_desc or "").strip()[:200] if operation_desc else None + + cls.objects.create( + operation_type=str(int(operation_type)), + operation_user=op_user, + operation_time=now, + operation_desc=desc, + inquiry_no=(inquiry_no or "")[:20], + quotation_no=qn, + per_status=(per_str[:50] if per_str else None), + cur_status=(cur_str[:100] if cur_str else None), + is_show_user=is_show_user, + purchase_type=int(purchase_type), + ) + + @classmethod + def try_append(cls, **kwargs) -> None: + """写入失败不影响主流程,仅记录异常日志。""" + try: + cls.append(**kwargs) + except Exception: + logger.exception("写入询价操作日志失败") + + def __str__(self) -> str: + return f"{self.inquiry_no}-{self.quotation_no}" + + +class MiscProcMaterialMinPrices(CoreModel): + """杂采材料制程最低价信息表(""" + + ITEM_NUM_CHOICES = ( + ("1", "重量"), + ("2", "单价"), + ) + + part_id = models.CharField(max_length=50, db_column="partid", verbose_name="采购价料号") + material_spec = models.CharField(max_length=20, db_column="material_spec", verbose_name="材料规格") + currency_code = models.CharField(max_length=50, db_column="currency_code", verbose_name="货币代码") + item_num = models.CharField(max_length=20, db_column="item_num", verbose_name="项次名") + lowest_value = models.CharField(max_length=20, db_column="lowest_value", verbose_name="最低值") + source_number = models.CharField(max_length=50, db_column="source_number", verbose_name="报价来源单号") + quotation_time = models.DateTimeField(db_column="quotation_time", verbose_name="报价时间") + + class Meta: + db_table = table_prefix + "misc_proc_material_min_prices" + verbose_name = "杂采材料制程最低价信息表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["part_id", "material_spec", "currency_code", "item_num"]), + ] + + def __str__(self) -> str: + return f"{self.part_id}-{self.material_spec}-{self.currency_code}-{self.item_num}" + + +class MiscProcProcessingMinPrices(CoreModel): + """杂采加工费用最低价信息表 + + 按采购件料号、货币代码存储各报价单加工费合计的最低值。 + """ + + part_id = models.CharField(max_length=50, db_column="partid", verbose_name="采购件料号") + currency_code = models.CharField(max_length=50, db_column="currencycode", verbose_name="货币代码") + lowest_value = models.CharField(max_length=20, db_column="lowest_vaule", verbose_name="加工费用最低价") + source_number = models.CharField(max_length=50, null=True, blank=True, db_column="sourcenumber", verbose_name="报价来源单号") + quotation_time = models.DateTimeField(null=True, blank=True, db_column="quotationtime", verbose_name="报价时间") + + class Meta: + db_table = table_prefix + "misc_proc_processing_min_prices" + verbose_name = "杂采加工费用最低价信息表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime", "id") + indexes = [ + models.Index(fields=["part_id", "currency_code"]), + ] + + def __str__(self) -> str: + return f"{self.part_id}-{self.currency_code}-{self.lowest_value}" \ No newline at end of file diff --git a/backend/apps/pisadmin/miscprocurement/serializers.py b/backend/apps/pisadmin/miscprocurement/serializers.py new file mode 100644 index 0000000..b055f1a --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/serializers.py @@ -0,0 +1,1636 @@ +import json + +from django.db import transaction +from django.db.models import Max +from django.utils import timezone +from rest_framework import serializers + +from .models import ( + MiscProcurementMaterialInfo, + MiscProcurementStationInfo, + MiscProcMaterial, + MiscLowPriceHeader, + MiscLowPriceDetail, + MiscNegotiationRecords, + RFQOperationLogs, + Inquiry, + InquirySupplier, + InquiryAttachment, + InquiryMaterialCost, + InquiryProcessCost, + InquiryOtherCost, + InquiryProfitCost, + InquiryRfqItem, + CostEstimateTemplateHead, + CostEstimateTemplateBody, + MiscProcMaterialMinPrices, + MiscProcProcessingMinPrices, +) +from apps.pisadmin.basicinfo.models import Company +from apps.pisadmin.basicinfo.models import Unit +from apps.pisadmin.basicinfo.models import SystemNoRule +from dvadmin.utils.serializers import CustomModelSerializer + + +def get_request_username(serializer) -> str: + request = serializer.context.get("request") + user = getattr(request, "user", None) if request else None + if not user: + return "" + return ( + getattr(user, "username", "") + or getattr(user, "name", "") + or getattr(user, "nick_name", "") + or getattr(user, "nickname", "") + or "" + ) + + +def resolve_inquiry_template_version(template_no: str, preferred_version=None): + """ + 从 t_CostEstimate_Template_Head(CostEstimateTemplateHead)解析询价单应锁定的模板版本号。 + 与 InquiryViewSet._get_template_prefill_fields 一致:仅 status=1(已确认);若 preferred_version + 存在且对应主表行存在则用之,否则取该 template_no 下最高 version。 + """ + tn = str(template_no or "").strip() + if not tn: + return None + base_qs = CostEstimateTemplateHead.objects.filter(template_no=tn, status=1) + head = None + if preferred_version is not None and preferred_version != "": + try: + ver = int(preferred_version) + except (TypeError, ValueError): + ver = None + if ver is not None: + head = base_qs.filter(version=ver).first() + if head is None: + head = base_qs.order_by("-version", "-id").first() + if not head: + return None + return int(head.version) + + +def get_request_username_from_request(request) -> str: + if not request: + return "" + user = getattr(request, "user", None) + if not user: + return "" + return ( + getattr(user, "username", "") + or getattr(user, "name", "") + or getattr(user, "nick_name", "") + or getattr(user, "nickname", "") + or "" + ) + + +def normalize_unit_code(value): + text = str(value or "").strip() + if not text: + return None + + text = text[:20] + matched = Unit.objects.filter(unitcode=text).only("unitcode").first() + if matched and matched.unitcode: + return str(matched.unitcode).strip()[:20] + + matched = Unit.objects.filter(unitname=text).only("unitcode", "unitname").first() + if matched: + resolved = str(matched.unitcode or matched.unitname or text).strip() + return resolved[:20] if resolved else None + + return text + + +SUPPLIER_BEHAVIOR_TO_CODE = { + "prefill_locked": 1, + "prefill_editable": 2, + "hidden_required": 3, + "hidden_optional": 4, + "required": 5, + "optional": 6, +} + +SUPPLIER_CODE_TO_BEHAVIOR = {value: key for key, value in SUPPLIER_BEHAVIOR_TO_CODE.items()} + +ATTACHMENT_TYPE_TO_CODE = { + "drawing": 1, + "产品图纸": 1, + "1": 1, + 1: 1, + "tender": 2, + "招标文件": 2, + "2": 2, + 2: 2, + "other": 3, + "其它文件": 3, + "其他文件": 3, + "3": 3, + 3: 3, +} + + +def normalize_purchaser_required(value, is_computed=0): + if is_computed: + return 0 + if value in (None, ""): + return 0 + if isinstance(value, bool): + return 1 if value else 0 + try: + return 1 if int(value) else 0 + except (TypeError, ValueError): + if isinstance(value, str): + return 1 if value.strip().lower() in ("true", "yes", "y", "on") else 0 + return 0 + + +def normalize_supplier_required(value, purchaser_required=0, supplier_editable=None, is_computed=0): + if is_computed: + return 0 + if isinstance(value, str): + text = value.strip() + if not text: + return 0 + if text in SUPPLIER_BEHAVIOR_TO_CODE: + return SUPPLIER_BEHAVIOR_TO_CODE[text] + try: + code = int(text) + if 0 <= code <= 6: + return code + except ValueError: + pass + elif isinstance(value, bool): + if purchaser_required: + if value: + return 2 if supplier_editable else 1 + return 4 if supplier_editable else 3 + return 5 if value else 6 + elif value is not None: + try: + code = int(value) + if 0 <= code <= 6: + return code + except (TypeError, ValueError): + pass + + if purchaser_required: + if supplier_editable is True: + return 4 + if supplier_editable is False: + return 3 + return 0 + + +def get_supplier_behavior(value, purchaser_required=0, supplier_editable=None): + code = normalize_supplier_required(value, purchaser_required=purchaser_required, supplier_editable=supplier_editable) + return SUPPLIER_CODE_TO_BEHAVIOR.get(code, "") + + +def normalize_attachment_file_type(value): + if value in (None, ""): + return 3 + if value in ATTACHMENT_TYPE_TO_CODE: + return ATTACHMENT_TYPE_TO_CODE[value] + text = str(value).strip() + if text in ATTACHMENT_TYPE_TO_CODE: + return ATTACHMENT_TYPE_TO_CODE[text] + try: + code = int(text) + if code in (1, 2, 3): + return code + except (TypeError, ValueError): + pass + return 3 + + +class MiscMaterialSerializer(CustomModelSerializer): + """杂采材料信息-序列化器""" + + class Meta: + model = MiscProcurementMaterialInfo + fields = "__all__" + read_only_fields = ["id"] + + +class MiscMaterialCreateUpdateSerializer(CustomModelSerializer): + """杂采材料信息 创建/更新""" + + materialtype = serializers.CharField(max_length=50, required=False, allow_blank=True) + factory = serializers.CharField(max_length=10, required=False, allow_blank=True) + + class Meta: + model = MiscProcurementMaterialInfo + fields = "__all__" + + +class MiscStationSerializer(CustomModelSerializer): + """杂采加工工站信息-序列化器""" + + class Meta: + model = MiscProcurementStationInfo + fields = "__all__" + read_only_fields = ["id"] + + +class MiscStationCreateUpdateSerializer(CustomModelSerializer): + """杂采加工工站信息 创建/更新""" + + stationname = serializers.CharField(max_length=50) + stationcode = serializers.CharField(max_length=20, required=False, allow_blank=True) + company_code = serializers.CharField(max_length=10, required=False, allow_blank=True) + + class Meta: + model = MiscProcurementStationInfo + fields = "__all__" + + +class MiscPartSerializer(CustomModelSerializer): + """杂采料号信息-序列化器""" + + class Meta: + model = MiscProcMaterial + fields = "__all__" + read_only_fields = ["id"] + + +class MiscPartCreateUpdateSerializer(CustomModelSerializer): + """杂采料号信息 创建/更新""" + + partid_name = serializers.CharField(max_length=50) + specification = serializers.CharField(max_length=50) + unit = serializers.CharField(max_length=50) + + class Meta: + model = MiscProcMaterial + fields = "__all__" + + +class MiscLowPriceHeaderSerializer(CustomModelSerializer): + """比价-制程最低价主表:开启/确认比价时写入;材料一行(重量×单价×数量)、加工一行(无次表、不按工站;合计最小,全 0/空也落库)、包装费(3)、运输费(4)、利润率(6);杂采不写管销研(5)。 + souce_no:材料行为次表来源聚合(如 W:报价单号;U:报价单号或厂区);加工行为合计最小的报价单单号,无报价时回退询价单号。""" + + class Meta: + model = MiscLowPriceHeader + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] + + +class MiscLowPriceDetailSerializer(CustomModelSerializer): + """比价-制程最低价记录次表(材料:最低重量、最低单价各一行;souce_no 为对应报价单单号或杂采交易厂区代码)。""" + + class Meta: + model = MiscLowPriceDetail + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] + + +class MiscNegotiationRecordsSerializer(CustomModelSerializer): + """杂采议价记录表""" + + class Meta: + model = MiscNegotiationRecords + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] + + +class RFQOperationLogsSerializer(CustomModelSerializer): + """询价单操作日志(rfq_operation_logs)。 + + 列表查询:``GET /api/.../miscprocurement/inquiry/{id}/operation_logs/``(按询价单主键, + 返回该单 ``inquiry_no`` 下全部日志,时间倒序)。 + """ + + operation_type = serializers.IntegerField(required=True) + operation_time = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + + class Meta: + model = RFQOperationLogs + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] + extra_kwargs = { + "inquiry_no": {"required": True, "allow_blank": False}, + "quotation_no": {"required": True, "allow_blank": False}, + "purchase_type": {"required": True}, + } + + def validate_operation_type(self, value): + valid_values = {choice[0] for choice in RFQOperationLogs.OPERATION_TYPE_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("操作类型取值不合法") + return value + + def validate_purchase_type(self, value): + valid_values = {choice[0] for choice in RFQOperationLogs.PURCHASE_TYPE_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("采购类别取值不合法") + return value + + def _normalize_operation_type(self, validated_data: dict) -> None: + ot = validated_data.get("operation_type") + if ot is not None and not isinstance(ot, str): + validated_data["operation_type"] = str(int(ot)) + + def create(self, validated_data): + self._normalize_operation_type(validated_data) + return super().create(validated_data) + + def update(self, instance, validated_data): + self._normalize_operation_type(validated_data) + return super().update(instance, validated_data) + + def _supplier_count_for_inquiry_publish(self, inquiry_no: str) -> int: + """询价单发布日志行:当前受邀供应商家数(distinct supplier_code)。""" + from apps.pisadmin.miscprocurement.models import InquirySupplier + + inq = (inquiry_no or "").strip() + if not inq: + return 0 + return ( + InquirySupplier.objects.filter(inquiry_no=inq) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + + def _quote_ended_supplier_submit_and_overdue_counts(self, inquiry_no: str): + """ + 询价单进入「报价结束」后:已提交报价(3)与已过期/逾期未报(4)的供应商家数(按 supplier_code 去重)。 + """ + from apps.pissupplier.models import QuotationMaster + + inq = (inquiry_no or "").strip() + if not inq: + return 0, 0 + qs = QuotationMaster.objects.filter(inquiry_no=inq) + submitted = ( + qs.filter(status=3) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + overdue = ( + qs.filter(status=4) + .values_list("supplier_code", flat=True) + .distinct() + .count() + ) + return submitted, overdue + + def to_representation(self, instance): + data = super().to_representation(instance) + ot = data.get("operation_type") + if ot is not None and ot != "": + try: + data["operation_type"] = int(ot) + except (TypeError, ValueError): + pass + try: + iot = int(data.get("operation_type")) if data.get("operation_type") is not None else None + except (TypeError, ValueError): + iot = None + inq_no = str(getattr(instance, "inquiry_no", "") or "") + if iot == 3: + data["supplier_count"] = self._supplier_count_for_inquiry_publish(inq_no) + else: + data["supplier_count"] = None + + cur_label = (data.get("cur_status") or getattr(instance, "cur_status", None) or "").strip() + if iot == 6 and cur_label == "报价结束": + sub_n, ovd_n = self._quote_ended_supplier_submit_and_overdue_counts(inq_no) + data["quote_ended_submitted_supplier_count"] = sub_n + data["quote_ended_overdue_supplier_count"] = ovd_n + else: + data["quote_ended_submitted_supplier_count"] = None + data["quote_ended_overdue_supplier_count"] = None + return data + + +class MiscNegotiationRecordBatchItemSerializer(serializers.Serializer): + """比价保存时按报价单维度的议价行""" + + quotation_no = serializers.CharField(max_length=20) + supplier_code = serializers.CharField(max_length=50, required=False, allow_blank=True) + is_awarded = serializers.IntegerField(required=False, default=0) + bargaining_price = serializers.DecimalField( + max_digits=12, decimal_places=4, required=False, allow_null=True + ) + total_price_excl_tax = serializers.DecimalField( + max_digits=12, decimal_places=4, required=False, allow_null=True + ) + total_price_incl_tax = serializers.DecimalField( + max_digits=12, decimal_places=4, required=False, allow_null=True + ) + + +class MiscNegotiationSaveSerializer(serializers.Serializer): + """比价/议价结果批量写入杂采议价记录表。part_id 可选,缺省时由服务端按 InquiryRfqItem 首行解析。""" + + part_id = serializers.CharField(max_length=50, required=False, allow_blank=True, allow_null=True) + records = MiscNegotiationRecordBatchItemSerializer(many=True) + + +class CostEstimateTemplateBodySerializer(serializers.ModelSerializer): + supplier_behavior = serializers.SerializerMethodField() + item_name_en = serializers.CharField(required=False, allow_blank=True, allow_null=True) + item_name_vn = serializers.CharField(required=False, allow_blank=True, allow_null=True) + + def to_internal_value(self, data): + if hasattr(data, "copy"): + data = data.copy() + if isinstance(data, dict) and "is_fixed" not in data and "item_category" in data: + data["is_fixed"] = data.get("item_category") + return super().to_internal_value(data) + + def to_representation(self, instance): + data = super().to_representation(instance) + is_computed = int(getattr(instance, "is_computed", 0) or 0) + if is_computed == 1: + data["purchaser_required"] = 0 + data["supplier_required"] = 0 + data["supplier_behavior"] = "" + return data + + def get_supplier_behavior(self, obj): + if int(getattr(obj, "is_computed", 0) or 0) == 1: + return "" + return get_supplier_behavior(getattr(obj, "supplier_required", 0), getattr(obj, "purchaser_required", 0)) + + class Meta: + model = CostEstimateTemplateBody + fields = [ + "id", + "cost_category", + "item_order", + "item_no", + "item_name_cn", + "item_name_en", + "item_name_vn", + "is_fixed", + "is_computed", + "purchaser_required", + "supplier_required", + "supplier_behavior", + "remark", + "version", + ] + + +class CostEstimateTemplateHeadSerializer(CustomModelSerializer): + # 模型上 template_no 无 blank=True,ModelSerializer 自动字段会 required=True; + # 创建时由 create()/_generate_template_no() 写入,故对入参只读,避免「模板编号必填」校验错误。 + template_no = serializers.CharField(read_only=True) + template_desc = serializers.CharField(required=False, allow_blank=True, allow_null=True) + items = CostEstimateTemplateBodySerializer(many=True, required=False) + + class Meta: + model = CostEstimateTemplateHead + fields = [ + "id", + "template_no", + "template_name", + "procurement_category", + "is_bom", + "acti", + "template_desc", + "is_can_add_materials", + "is_can_add_process", + "version", + "status", + "create_user", + "create_time", + "update_user", + "update_time", + "items", + ] + read_only_fields = ["id", "create_time", "update_time"] + + def validate_status(self, value): + if value is None: + return value + allowed = {c[0] for c in CostEstimateTemplateHead.STATUS_CHOICES} + try: + v = int(value) + except (TypeError, ValueError): + raise serializers.ValidationError("状态必须为整数") + if v not in allowed: + choices_txt = ";".join(f"{code}({label})" for code, label in CostEstimateTemplateHead.STATUS_CHOICES) + raise serializers.ValidationError(f"状态无效,允许:{choices_txt}") + return v + + def _generate_template_no(self) -> str: + date_part = timezone.now().strftime("%Y%m%d") + base = f"CT{date_part}" + with transaction.atomic(): + last = ( + CostEstimateTemplateHead.objects.select_for_update() + .filter(template_no__startswith=base) + .order_by("-template_no") + .first() + ) + if last and last.template_no and len(last.template_no) >= len(base) + 3: + try: + seq = int(last.template_no[-3:]) + 1 + except ValueError: + seq = 1 + else: + seq = 1 + if seq > 999: + raise serializers.ValidationError("当日编号已达上限,请联系管理员") + return f"{base}{seq:03d}" + + def _upsert_items(self, head: CostEstimateTemplateHead, items, current_user: str = "", current_time=None): + # 同一模板编号下可多版本;只删除当前主表行对应版本号的明细,避免误删其它版本 + head_ver = getattr(head, "version", None) + if head_ver is None: + head_ver = 1 + CostEstimateTemplateBody.objects.filter(template_no=head.template_no, version=head_ver).delete() + bulk = [] + if current_time is None: + current_time = timezone.now() + + def to_int(v, default=0): + if v is None or v == "": + return default + if isinstance(v, bool): + return 1 if v else 0 + try: + return int(v) + except Exception: + # 兼容 'true'/'false' + if isinstance(v, str): + lv = v.strip().lower() + if lv in ("true", "yes", "y", "on"): + return 1 + if lv in ("false", "no", "n", "off"): + return 0 + return default + + for idx, item in enumerate(items or [], start=1): + is_computed_raw = ( + item.get("is_computed", None) + if isinstance(item, dict) + else None + ) + if is_computed_raw is None and isinstance(item, dict): + is_computed_raw = item.get("isComputed", item.get("autoFill", 0)) + is_computed = to_int(is_computed_raw, 0) + purchaser_required = normalize_purchaser_required(item.get("purchaser_required", 0), is_computed=is_computed) + # 兼容 item_name_en/vn 多种键名,确保编辑时用户修改的国际化名称能正确写入 + item_name_cn = (item.get("item_name_cn") or item.get("itemNameCn") or "").strip() or "" + item_name_en = (item.get("item_name_en") or item.get("itemNameEn") or "").strip() or None + item_name_vn = (item.get("item_name_vn") or item.get("itemNameVn") or "").strip() or None + body_version = item.get("version") + if body_version is None or body_version == "": + body_version = getattr(head, "version", 1) or 1 + else: + body_version = to_int(body_version, getattr(head, "version", 1) or 1) + bulk.append( + CostEstimateTemplateBody( + template_no=head.template_no, + cost_category=item.get("cost_category", ""), + item_order=item.get("item_order", idx), + item_no=item.get("item_no", str(idx)), + item_name_cn=item_name_cn, + item_name_en=item_name_en, + item_name_vn=item_name_vn, + is_fixed=int(item.get("is_fixed", item.get("item_category", 0)) or 0), + is_computed=is_computed, + purchaser_required=purchaser_required, + supplier_required=normalize_supplier_required( + item.get("supplier_required", item.get("supplier_behavior")), + purchaser_required=purchaser_required, + supplier_editable=item.get("supplier_editable"), + is_computed=is_computed, + ), + remark=item.get("remark"), + version=body_version, + create_user=current_user or None, + create_time=current_time, + update_user=current_user or None, + update_time=current_time, + ) + ) + if bulk: + CostEstimateTemplateBody.objects.bulk_create(bulk) + + def create(self, validated_data): + items = validated_data.pop("items", []) + current_user = get_request_username(self) + current_time = timezone.now() + if not validated_data.get("template_no"): + validated_data["template_no"] = self._generate_template_no() + if "version" not in validated_data or validated_data.get("version") is None: + validated_data["version"] = 1 + validated_data["create_user"] = current_user or validated_data.get("create_user") + validated_data["create_time"] = current_time + validated_data["update_user"] = current_user or validated_data.get("update_user") + validated_data["update_time"] = current_time + head = CostEstimateTemplateHead.objects.create(**validated_data) + self._upsert_items(head, items, current_user=current_user, current_time=current_time) + return head + + def update(self, instance, validated_data): + # 重要:若未传 items,不要清空明细 + items = validated_data.pop("items", None) + current_user = get_request_username(self) + current_time = timezone.now() + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.update_user = current_user or instance.update_user + instance.update_time = current_time + instance.save() + if items is not None: + self._upsert_items(instance, items, current_user=current_user, current_time=current_time) + return instance + + +class CostEstimateTemplateNewVersionSerializer(serializers.Serializer): + """ + 已确认模板派生新版本:沿用同一 template_no,版本号由服务端在同编号下取 max(version)+1; + 与「添加」初始模板(自动生成新编号)的业务入口分离。 + """ + + template_name = serializers.CharField(max_length=50) + procurement_category = serializers.CharField(max_length=4) + is_bom = serializers.CharField(max_length=4, required=False, default="Y") + acti = serializers.CharField(max_length=4, required=False, allow_blank=True, default="Y") + template_desc = serializers.CharField(required=False, allow_blank=True, allow_null=True, default="") + is_can_add_materials = serializers.IntegerField(required=False, default=0) + is_can_add_process = serializers.IntegerField(required=False, default=0) + items = CostEstimateTemplateBodySerializer(many=True, required=False) + + +def _cost_item_to_int(v, default=0): + """与 CostEstimateTemplateHeadSerializer._upsert_items 内 to_int 一致,用于明细比对。""" + if v is None or v == "": + return default + if isinstance(v, bool): + return 1 if v else 0 + try: + return int(v) + except Exception: + if isinstance(v, str): + lv = v.strip().lower() + if lv in ("true", "yes", "y", "on"): + return 1 + if lv in ("false", "no", "n", "off"): + return 0 + return default + + +def _cost_template_item_row_tuple_from_dict(item: dict, idx: int) -> tuple: + """将请求体中的单条明细规范为与落库语义一致的比对元组(不含 template_no/version/id)。""" + is_computed_raw = item.get("is_computed", None) + if is_computed_raw is None: + is_computed_raw = item.get("isComputed", item.get("autoFill", 0)) + is_computed = _cost_item_to_int(is_computed_raw, 0) + purchaser_required = normalize_purchaser_required(item.get("purchaser_required", 0), is_computed=is_computed) + supplier_required = normalize_supplier_required( + item.get("supplier_required", item.get("supplier_behavior")), + purchaser_required=purchaser_required, + supplier_editable=item.get("supplier_editable"), + is_computed=is_computed, + ) + item_order = _cost_item_to_int(item.get("item_order", idx), idx) + return ( + str(item.get("cost_category", "") or ""), + item_order, + str(item.get("item_no", str(idx)) or ""), + (item.get("item_name_cn") or item.get("itemNameCn") or "").strip(), + (item.get("item_name_en") or item.get("itemNameEn") or "").strip(), + (item.get("item_name_vn") or item.get("itemNameVn") or "").strip(), + _cost_item_to_int(item.get("is_fixed", item.get("item_category", 0)), 0), + is_computed, + purchaser_required, + supplier_required, + (item.get("remark") or "").strip(), + ) + + +def _cost_template_item_row_tuple_from_body(obj: CostEstimateTemplateBody) -> tuple: + is_computed = _cost_item_to_int(getattr(obj, "is_computed", 0), 0) + purchaser_required = normalize_purchaser_required(getattr(obj, "purchaser_required", 0), is_computed=is_computed) + supplier_required = normalize_supplier_required( + getattr(obj, "supplier_required", 0), + purchaser_required=purchaser_required, + supplier_editable=None, + is_computed=is_computed, + ) + return ( + str(getattr(obj, "cost_category", "") or ""), + _cost_item_to_int(getattr(obj, "item_order", 0), 0), + str(getattr(obj, "item_no", "") or ""), + (getattr(obj, "item_name_cn", None) or "").strip(), + (getattr(obj, "item_name_en", None) or "").strip(), + (getattr(obj, "item_name_vn", None) or "").strip(), + _cost_item_to_int(getattr(obj, "is_fixed", 0), 0), + is_computed, + purchaser_required, + supplier_required, + (getattr(obj, "remark", None) or "").strip(), + ) + + +def _cost_template_items_compare_signature_from_payload(items_clean: list) -> tuple: + rows = [] + for idx, row in enumerate(items_clean or [], start=1): + if isinstance(row, dict): + rows.append(_cost_template_item_row_tuple_from_dict(row, idx)) + rows.sort(key=lambda t: (t[1], t[2])) + return tuple(rows) + + +def _cost_template_items_compare_signature_from_db(template_no: str, version: int) -> tuple: + qs = CostEstimateTemplateBody.objects.filter(template_no=template_no, version=version).order_by("item_order", "id") + rows = [_cost_template_item_row_tuple_from_body(r) for r in qs] + rows.sort(key=lambda t: (t[1], t[2])) + return tuple(rows) + + +@transaction.atomic +def create_cost_template_new_version( + source_head: CostEstimateTemplateHead, + validated: dict, + request, +) -> CostEstimateTemplateHead: + """ + 从已确认主表行创建新版本主表行并写入明细;不调用 HeadSerializer.create,不复用「初始模板」编号生成逻辑。 + """ + template_no = source_head.template_no + # 锁定同编号行,避免并发下 version 冲突 + list(CostEstimateTemplateHead.objects.select_for_update().filter(template_no=template_no)) + + items_in = validated.get("items") or [] + items_clean = [] + for row in items_in: + if isinstance(row, dict): + # 忽略客户端带的明细 version,一律与新建主表 version 对齐(避免与 max+1 不一致) + row = {k: v for k, v in row.items() if k not in ("id", "version")} + items_clean.append(row) + + prev_ver = int(getattr(source_head, "version", 1) or 1) + sig_new = _cost_template_items_compare_signature_from_payload(items_clean) + sig_old = _cost_template_items_compare_signature_from_db(template_no, prev_ver) + if sig_new == sig_old: + raise serializers.ValidationError( + {"items": ["新版本明细与上一版本相同,无需创建新版本"]} + ) + + agg = CostEstimateTemplateHead.objects.filter(template_no=template_no).aggregate(mx=Max("version")) + max_v = agg["mx"] + next_v = int(max_v if max_v is not None else 0) + 1 + + current_user = get_request_username_from_request(request) + current_time = timezone.now() + acti = validated.get("acti") + if acti in (None, ""): + acti = "Y" + is_bom = validated.get("is_bom") or "Y" + + head = CostEstimateTemplateHead.objects.create( + template_no=template_no, + template_name=validated["template_name"], + procurement_category=validated["procurement_category"], + is_bom=is_bom, + acti=acti, + template_desc=validated.get("template_desc") or "", + is_can_add_materials=int(validated.get("is_can_add_materials") or 0), + is_can_add_process=int(validated.get("is_can_add_process") or 0), + version=next_v, + status=0, + create_user=current_user or None, + create_time=current_time, + update_user=current_user or None, + update_time=current_time, + ) + + helper = CostEstimateTemplateHeadSerializer() + helper._upsert_items(head, items_clean, current_user=current_user, current_time=current_time) + return head + + +class InquirySupplierSerializer(serializers.ModelSerializer): + class Meta: + model = InquirySupplier + fields = [ + "id", + "inquiry_no", + "part_id", + "supplier_code", + "supplier_name", + "contact_person", + "contact_phone", + "contact_email", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class NestedInquirySupplierSerializer(InquirySupplierSerializer): + class Meta(InquirySupplierSerializer.Meta): + validators = [] + + +class InquiryAttachmentSerializer(serializers.ModelSerializer): + """采购端询价单附件读写;供应商在报价详情中通过 `QuotationMasterSerializer.inquiry_attachments` 只读查看。发布时不复制到报价单附件子表。""" + + file_type = serializers.CharField() + + def validate_file_type(self, value): + return normalize_attachment_file_type(value) + + def to_representation(self, instance): + data = super().to_representation(instance) + data["file_type"] = normalize_attachment_file_type(data.get("file_type")) + return data + + class Meta: + model = InquiryAttachment + fields = [ + "id", + "inquiry_no", + "part_id", + "file_type", + "file_name", + "file_path", + "upload_time", + "upload_user", + ] + read_only_fields = ["id", "upload_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class NestedInquiryAttachmentSerializer(InquiryAttachmentSerializer): + class Meta(InquiryAttachmentSerializer.Meta): + validators = [] + + +class InquiryMaterialCostSerializer(serializers.ModelSerializer): + class Meta: + model = InquiryMaterialCost + fields = [ + "id", + "inquiry_no", + "part_id", + "material_spec", + "length", + "width", + "height", + "unit_price", + "qty", + "specific_gravity", + "material_cost", + "weight", + "remark", + "option_json", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class InquiryProcessCostSerializer(serializers.ModelSerializer): + def to_representation(self, instance): + data = super().to_representation(instance) + data["unit"] = normalize_unit_code(data.get("unit")) + return data + + class Meta: + model = InquiryProcessCost + fields = [ + "id", + "inquiry_no", + "part_id", + "process_station", + "unit", + "unit_rate", + "process_qty", + "process_price", + "remark", + "option_json", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class InquiryOtherCostSerializer(serializers.ModelSerializer): + class Meta: + model = InquiryOtherCost + fields = [ + "id", + "inquiry_no", + "part_id", + "packaging_cost", + "transportation_cost", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class InquiryProfitCostSerializer(serializers.ModelSerializer): + class Meta: + model = InquiryProfitCost + fields = [ + "id", + "inquiry_no", + "part_id", + "tax_rate", + "profit_rate", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class InquiryRfqItemSerializer(serializers.ModelSerializer): + class Meta: + model = InquiryRfqItem + fields = [ + "id", + "inquiry_no", + "part_id", + "product_name", + "unit", + "qty", + "is_bom", + "unit_price", + "total_material_cost", + "total_processing_cost", + "total_other_expense", + "total_opex_amt", + "profit_rate", + "total_price_excl_tax", + "tax_rate", + "total_price_incl_tax", + "option_json", + "create_time", + "create_user", + ] + read_only_fields = ["id", "create_time"] + extra_kwargs = {"inquiry_no": {"required": False, "allow_null": True}} + + +class InquirySerializer(CustomModelSerializer): + MATERIAL_COST_OPTION_EXCLUDE_KEYS = { + "partNo", + "part_no", + "partId", + "part_id", + "material", + "material_spec", + "length", + "width", + "height", + "specificgravity", + "specific_gravity", + "qty", + "weight", + "unitPrice", + "unitprice", + "unit_price", + "price", + "material_cost", + "materialCost", + "material_fee", + "materialFee", + "remark", + } + PROCESS_COST_OPTION_EXCLUDE_KEYS = { + "partNo", + "part_no", + "partId", + "part_id", + "process_station", + "unit", + "unitrate", + "unit_rate", + "rate", + "processqty", + "process_qty", + "qty", + "quantity", + "processprice", + "process_price", + "process_cost", + "remark", + } + RFQ_ITEM_OPTION_EXCLUDE_KEYS = { + "partNo", + "part_no", + "partId", + "part_id", + "desc", + "product_name", + "productName", + "unit", + "qty", + "quantity", + "price", + "unitPrice", + "unitprice", + "unit_price", + "amount", + "total_material_cost", + "total_processing_cost", + "total_other_expense", + "total_opex_amt", + "profit_rate", + "tax_rate", + "total_price_excl_tax", + "total_price_incl_tax", + } + + suppliers = NestedInquirySupplierSerializer(many=True, required=False) + attachments = NestedInquiryAttachmentSerializer(many=True, required=False) + material_costs = InquiryMaterialCostSerializer(many=True, required=False) + process_costs = InquiryProcessCostSerializer(many=True, required=False) + other_costs = InquiryOtherCostSerializer(many=True, required=False) + profit_costs = InquiryProfitCostSerializer(many=True, required=False) + rfq_items = InquiryRfqItemSerializer(many=True, required=False) + # 列表/详情展示:company_code → 公司信息简称(同请求内按代码缓存) + company_short_name = serializers.SerializerMethodField(read_only=True) + # 上阶物料首行:与 misc_rfq_items 一致,供列表/比价弹窗回显采购件料号、名称(主表无独立字段) + part_no = serializers.SerializerMethodField(read_only=True) + part_name = serializers.SerializerMethodField(read_only=True) + # 税率:优先上阶物料行 tax_rate,否则同料号税费利润行(proc_inquiry_profit_cost.tax_rate) + tax_rate = serializers.SerializerMethodField(read_only=True) + quote_deadline = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + bid_start_time = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + bid_end_time = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + + def get_company_short_name(self, obj): + code = (getattr(obj, "company_code", None) or "").strip() + if not code: + return "" + cache = self.context.setdefault("_company_short_by_code", {}) + if code not in cache: + row = Company.objects.filter(company_code=code).only("company_short_name", "company_name").first() + if row: + short = (row.company_short_name or row.company_name or "").strip() + else: + short = "" + cache[code] = short or code + return cache[code] + + def _first_rfq_item(self, obj: Inquiry): + qs = getattr(obj, "rfq_items", None) + if qs is None: + return None + return qs.order_by("id").first() + + def get_part_no(self, obj): + item = self._first_rfq_item(obj) + if not item: + return "" + v = getattr(item, "part_id", None) + return "" if v is None else str(v).strip() + + def get_part_name(self, obj): + item = self._first_rfq_item(obj) + if not item: + return "" + v = getattr(item, "product_name", None) + return "" if v is None else str(v).strip() + + def _profit_cost_for_first_part(self, obj: Inquiry): + """首条上阶物料对应料号的税费利润行;无料号时取首条利润行。""" + rfq = self._first_rfq_item(obj) + qs = getattr(obj, "profit_costs", None) + if qs is None: + return None + if rfq: + pid = getattr(rfq, "part_id", None) + if pid is not None and str(pid).strip() != "": + row = qs.filter(part_id=pid).order_by("id").first() + if row: + return row + return qs.order_by("id").first() + + def get_tax_rate(self, obj): + item = self._first_rfq_item(obj) + if item: + v = getattr(item, "tax_rate", None) + if v is not None and str(v).strip() != "": + return str(v).strip() + pc = self._profit_cost_for_first_part(obj) + if pc: + v = getattr(pc, "tax_rate", None) + if v is not None and str(v).strip() != "": + return str(v).strip() + return "" + + def to_internal_value(self, data): + if hasattr(data, "copy"): + data = data.copy() + if isinstance(data, dict) and "target_price" not in data and "inquiry_price" in data: + data["target_price"] = data.get("inquiry_price") + return super().to_internal_value(data) + + class Meta: + model = Inquiry + fields = "__all__" + # CoreModel 审计 + 主表业务审计:禁止客户端写入篡改;create/update 中仍可向 validated_data 注入业务字段 + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "create_time", + "create_user", + "update_time", + "update_user", + ] + extra_kwargs = { + "inquiry_no": {"required": False, "allow_blank": True, "allow_null": True}, + # 由 resolve_inquiry_template_version 根据 template 从 CostEstimateTemplateHead 写入 + "template_version": {"required": False, "allow_null": True}, + "buying_method": {"required": False, "allow_null": True}, + } + + def validate_buying_method(self, value): + if value is None: + return value + valid_values = {choice[0] for choice in Inquiry.BUYING_METHOD_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("采购方式(寻源方式)取值不合法") + return value + + @staticmethod + def _effective_inquiry_field(attrs, instance, field_name): + """合并局部更新:未出现在 attrs 中的字段沿用 instance。""" + if field_name in attrs: + return attrs[field_name] + if instance is not None: + return getattr(instance, field_name, None) + return None + + def _resolve_buying_method(self, attrs, instance): + bm = attrs.get("buying_method") + if bm is None and instance is not None: + bm = getattr(instance, "buying_method", None) + if bm is None: + return 1 + try: + return int(bm) + except (TypeError, ValueError): + return 1 + + def _validate_inquiry_deadline_fields(self, attrs, instance): + """与前端一致:询价(1)必填报价截止时;招标(2)必填投标起止时间且结束晚于开始。""" + bm = self._resolve_buying_method(attrs, instance) + errors = {} + if bm == 1: + qd = self._effective_inquiry_field(attrs, instance, "quote_deadline") + if qd is None: + errors["quote_deadline"] = "询价方式下须填写报价截止时间" + elif bm == 2: + bs = self._effective_inquiry_field(attrs, instance, "bid_start_time") + be = self._effective_inquiry_field(attrs, instance, "bid_end_time") + if bs is None: + errors["bid_start_time"] = "招标方式下须填写投标开始时间" + if be is None: + errors["bid_end_time"] = "招标方式下须填写投标截止时间" + if bs is not None and be is not None and bs >= be: + errors["bid_end_time"] = "投标截止时间须晚于投标开始时间" + if errors: + raise serializers.ValidationError(errors) + + def _apply_inquiry_deadline_fields_for_buying_method(self, attrs, instance): + """ + 询价(1):投标时间不入库。 + 招标(2):报价截止时不入库(与前端「投标开始/截止」互斥)。 + """ + bm = self._resolve_buying_method(attrs, instance) + if bm == 1: + attrs["bid_start_time"] = None + attrs["bid_end_time"] = None + elif bm == 2: + attrs["quote_deadline"] = None + + def validate(self, attrs): + instance = getattr(self, "instance", None) + + template_in_attrs = "template" in attrs + tv_in_attrs = "template_version" in attrs + + # 局部更新且未改模板/版本:不重新解析模板版本 + if instance and not template_in_attrs and not tv_in_attrs: + self._validate_inquiry_deadline_fields(attrs, instance) + self._apply_inquiry_deadline_fields_for_buying_method(attrs, instance) + return attrs + + template_no = attrs.get("template") + if template_no is not None: + template_no = str(template_no).strip() + elif instance is not None: + template_no = (instance.template or "").strip() + else: + template_no = "" + + if not template_no: + self._validate_inquiry_deadline_fields(attrs, instance) + self._apply_inquiry_deadline_fields_for_buying_method(attrs, instance) + return attrs + + preferred = attrs["template_version"] if tv_in_attrs else None + resolved = resolve_inquiry_template_version(template_no, preferred_version=preferred) + if resolved is None: + raise serializers.ValidationError( + { + "template": "所选询价模板无已确认版本,请先在「成本结构模板」中确认后再保存", + } + ) + attrs["template_version"] = resolved + self._validate_inquiry_deadline_fields(attrs, instance) + self._apply_inquiry_deadline_fields_for_buying_method(attrs, instance) + return attrs + + def _generate_code(self, validated_data: dict) -> str: + """ + 按 `SystemNoRule` 取号:rule_code=miscRFS,company_code 取表单或默认「通用」厂区。 + 组装见 `SystemNoRule.allocate_system_number`(prefix + factory_code + sequence_date + 流水)。 + """ + company_code = (validated_data.get("company_code") or "").strip() or SystemNoRule.DEFAULT_SYSTEM_NO_COMPANY_CODE + username = get_request_username(self) or None + return SystemNoRule.allocate_system_number(company_code, "miscRFS", username=username) + + def _upsert_suppliers(self, inquiry: Inquiry, suppliers): + """更新或创建供应商信息""" + InquirySupplier.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for supplier in suppliers or []: + bulk.append( + InquirySupplier( + inquiry_no=inquiry, + part_id=supplier.get("part_id", ""), + supplier_code=supplier.get("supplier_code", ""), + supplier_name=supplier.get("supplier_name", ""), + contact_person=supplier.get("contact_person"), + contact_phone=supplier.get("contact_phone"), + contact_email=supplier.get("contact_email"), + create_user=supplier.get("create_user") or current_user or None, + ) + ) + if bulk: + InquirySupplier.objects.bulk_create(bulk) + + def _upsert_attachments(self, inquiry: Inquiry, attachments): + """更新或创建附件信息""" + InquiryAttachment.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for attachment in attachments or []: + bulk.append( + InquiryAttachment( + inquiry_no=inquiry, + part_id=attachment.get("part_id", ""), + file_type=normalize_attachment_file_type(attachment.get("file_type")), + file_name=attachment.get("file_name", ""), + file_path=attachment.get("file_path", ""), + upload_user=attachment.get("upload_user") or current_user or None, + ) + ) + if bulk: + InquiryAttachment.objects.bulk_create(bulk) + + def _normalize_option_json(self, payload, excluded_keys): + if payload in (None, ""): + return None + + parsed = payload + if isinstance(payload, str): + payload = payload.strip() + if not payload: + return None + try: + parsed = json.loads(payload) + except (TypeError, ValueError): + return payload + + if not isinstance(parsed, dict): + try: + return json.dumps(parsed, ensure_ascii=False) + except (TypeError, ValueError): + return None + + filtered = {key: value for key, value in parsed.items() if key not in excluded_keys} + if not filtered: + return None + return json.dumps(filtered, ensure_ascii=False) + + def _normalize_material_option_json(self, row): + return self._normalize_option_json(row.get("option_json"), self.MATERIAL_COST_OPTION_EXCLUDE_KEYS) + + def _normalize_process_option_json(self, row): + return self._normalize_option_json(row.get("option_json"), self.PROCESS_COST_OPTION_EXCLUDE_KEYS) + + def _normalize_rfq_item_option_json(self, row): + return self._normalize_option_json(row.get("option_json"), self.RFQ_ITEM_OPTION_EXCLUDE_KEYS) + + def _upsert_material_costs(self, inquiry: Inquiry, material_costs): + """更新或创建材料成本明细""" + InquiryMaterialCost.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for row in material_costs or []: + bulk.append( + InquiryMaterialCost( + inquiry_no=inquiry, + part_id=row.get("part_id", ""), + material_spec=row.get("material_spec"), + length=row.get("length"), + width=row.get("width"), + height=row.get("height"), + unit_price=row.get("unit_price"), + qty=row.get("qty"), + specific_gravity=row.get("specific_gravity"), + material_cost=row.get("material_cost"), + weight=row.get("weight"), + remark=row.get("remark"), + option_json=self._normalize_material_option_json(row), + create_user=row.get("create_user") or current_user or None, + ) + ) + if bulk: + InquiryMaterialCost.objects.bulk_create(bulk) + + def _upsert_process_costs(self, inquiry: Inquiry, process_costs): + """更新或创建加工成本明细""" + InquiryProcessCost.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for row in process_costs or []: + bulk.append( + InquiryProcessCost( + inquiry_no=inquiry, + part_id=row.get("part_id", ""), + process_station=row.get("process_station"), + unit=normalize_unit_code(row.get("unit")), + unit_rate=row.get("unit_rate"), + process_qty=row.get("process_qty"), + process_price=row.get("process_price"), + remark=row.get("remark"), + option_json=self._normalize_process_option_json(row), + create_user=row.get("create_user") or current_user or None, + ) + ) + if bulk: + InquiryProcessCost.objects.bulk_create(bulk) + + def _upsert_other_costs(self, inquiry: Inquiry, other_costs): + """更新或创建其它成本明细""" + InquiryOtherCost.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for row in other_costs or []: + bulk.append( + InquiryOtherCost( + inquiry_no=inquiry, + part_id=row.get("part_id", ""), + packaging_cost=row.get("packaging_cost"), + transportation_cost=row.get("transportation_cost"), + create_user=row.get("create_user") or current_user or None, + ) + ) + if bulk: + InquiryOtherCost.objects.bulk_create(bulk) + + def _upsert_profit_costs(self, inquiry: Inquiry, profit_costs): + """更新或创建税费利润明细""" + InquiryProfitCost.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + for row in profit_costs or []: + bulk.append( + InquiryProfitCost( + inquiry_no=inquiry, + part_id=row.get("part_id", ""), + tax_rate=row.get("tax_rate"), + profit_rate=row.get("profit_rate"), + create_user=row.get("create_user") or current_user or None, + ) + ) + if bulk: + InquiryProfitCost.objects.bulk_create(bulk) + + def _upsert_rfq_items(self, inquiry: Inquiry, rfq_items): + """更新或创建采购产品明细""" + InquiryRfqItem.objects.filter(inquiry_no=inquiry).delete() + bulk = [] + current_user = get_request_username(self) + + def resolve_part_unit(part_id, unit): + unit_text = str(unit or "").strip() + if unit_text: + return unit_text[:10] + + if not part_id: + return None + + queryset = MiscProcMaterial.objects.filter(partid=part_id) + if inquiry.company_code: + matched = queryset.filter(company_code=inquiry.company_code).first() + if matched and matched.unit: + return str(matched.unit).strip()[:10] + + matched = queryset.first() + if matched and matched.unit: + return str(matched.unit).strip()[:10] + return None + + for row in rfq_items or []: + bulk.append( + InquiryRfqItem( + inquiry_no=inquiry, + part_id=row.get("part_id", ""), + product_name=row.get("product_name"), + unit=resolve_part_unit(row.get("part_id", ""), row.get("unit")), + qty=row.get("qty"), + is_bom=row.get("is_bom"), + unit_price=row.get("unit_price"), + total_material_cost=row.get("total_material_cost"), + total_processing_cost=row.get("total_processing_cost"), + total_other_expense=row.get("total_other_expense"), + total_opex_amt=row.get("total_opex_amt"), + profit_rate=row.get("profit_rate"), + total_price_excl_tax=row.get("total_price_excl_tax"), + tax_rate=row.get("tax_rate"), + total_price_incl_tax=row.get("total_price_incl_tax"), + option_json=self._normalize_rfq_item_option_json(row), + create_user=row.get("create_user") or current_user or None, + ) + ) + if bulk: + InquiryRfqItem.objects.bulk_create(bulk) + + def create(self, validated_data): + """ + 创建新的询价单记录,自动生成inquiry_no(询价单号) + """ + suppliers = validated_data.pop("suppliers", []) + attachments = validated_data.pop("attachments", []) + material_costs = validated_data.pop("material_costs", []) + process_costs = validated_data.pop("process_costs", []) + other_costs = validated_data.pop("other_costs", []) + profit_costs = validated_data.pop("profit_costs", []) + rfq_items = validated_data.pop("rfq_items", []) + current_user = get_request_username(self) + current_time = timezone.now() + + if not validated_data.get("inquiry_no"): + validated_data["inquiry_no"] = self._generate_code(validated_data) + # 询价单创建后默认进入“开立”,状态流转仅允许通过专用动作接口处理 + validated_data["status"] = 1 + validated_data.pop("confirm_user", None) + validated_data.pop("confirm_time", None) + validated_data.pop("release_user", None) + validated_data.pop("release_time", None) + validated_data["create_user"] = current_user or validated_data.get("create_user") + validated_data["update_user"] = current_user or validated_data.get("update_user") + validated_data["update_time"] = current_time + + inquiry = super().create(validated_data) + + # 创建相关的供应商和附件记录 + self._upsert_suppliers(inquiry, suppliers) + self._upsert_attachments(inquiry, attachments) + self._upsert_material_costs(inquiry, material_costs) + self._upsert_process_costs(inquiry, process_costs) + self._upsert_other_costs(inquiry, other_costs) + self._upsert_profit_costs(inquiry, profit_costs) + self._upsert_rfq_items(inquiry, rfq_items) + + return inquiry + + def update(self, instance, validated_data): + """ + 更新询价单记录 + """ + suppliers = validated_data.pop("suppliers", None) + attachments = validated_data.pop("attachments", None) + material_costs = validated_data.pop("material_costs", None) + process_costs = validated_data.pop("process_costs", None) + other_costs = validated_data.pop("other_costs", None) + profit_costs = validated_data.pop("profit_costs", None) + rfq_items = validated_data.pop("rfq_items", None) + current_user = get_request_username(self) + current_time = timezone.now() + + # 更新时不修改inquiry_no + validated_data.pop("inquiry_no", None) + # 状态流转和对应审计字段仅允许通过专用动作接口处理 + validated_data.pop("status", None) + validated_data.pop("confirm_user", None) + validated_data.pop("confirm_time", None) + validated_data.pop("release_user", None) + validated_data.pop("release_time", None) + validated_data["update_user"] = current_user or validated_data.get("update_user") + validated_data["update_time"] = current_time + inquiry = super().update(instance, validated_data) + + # 更新供应商和附件信息 + if suppliers is not None: + self._upsert_suppliers(inquiry, suppliers) + if attachments is not None: + self._upsert_attachments(inquiry, attachments) + if material_costs is not None: + self._upsert_material_costs(inquiry, material_costs) + if process_costs is not None: + self._upsert_process_costs(inquiry, process_costs) + if other_costs is not None: + self._upsert_other_costs(inquiry, other_costs) + if profit_costs is not None: + self._upsert_profit_costs(inquiry, profit_costs) + if rfq_items is not None: + self._upsert_rfq_items(inquiry, rfq_items) + + return inquiry + + +class MiscProcMaterialMinPricesSerializer(CustomModelSerializer): + """杂采材料制程最低价信息表序列化器""" + + class Meta: + model = MiscProcMaterialMinPrices + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] + + +class MiscProcProcessingMinPricesSerializer(CustomModelSerializer): + """杂采加工费用最低价信息表序列化器""" + + class Meta: + model = MiscProcProcessingMinPrices + fields = "__all__" + read_only_fields = [ + "id", + "create_datetime", + "update_datetime", + "creator", + "modifier", + "dept_belong_id", + ] diff --git a/backend/apps/pisadmin/miscprocurement/tasks.py b/backend/apps/pisadmin/miscprocurement/tasks.py new file mode 100644 index 0000000..db44d93 --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/tasks.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +""" +Celery 定时任务:报价截止时间到期邮件通知采购。 +""" +import logging +from typing import Dict, Any, List + +from django.utils import timezone + +from application.celery import app +from apps.pissupplier.models import QuotationMaster +from apps.pisadmin.miscprocurement.models import Inquiry + +logger = logging.getLogger(__name__) + + +def _is_in_cooldown(inquiry_no: str, cooldown_hours: int = 24) -> bool: + """ + 检查该询价单是否在冷却期内(24h内已成功发送过)。 + 复用 email_utils 的冷却判断逻辑。 + """ + from apps.pisadmin.basicinfo.views.email_utils import should_send_quote_timeout_reminder + return not should_send_quote_timeout_reminder(inquiry_no, cooldown_hours=cooldown_hours) + + +def _get_purchaser_emails(inquiry_no: str) -> List[str]: + """根据询价单号获取采购负责人邮箱列表。""" + from apps.pisadmin.basicinfo.views.email_utils import resolve_inquiry_purchaser_emails + inquiry = Inquiry.objects.filter(inquiry_no=inquiry_no).first() + if not inquiry: + return [] + return resolve_inquiry_purchaser_emails(inquiry) + + +def _build_expired_supplier_list(inquiry_no: str) -> List[Dict[str, Any]]: + """ + 构建某询价单下所有已到期未报价供应商列表。 + """ + now = timezone.now() + rows = QuotationMaster.objects.filter( + inquiry_no=inquiry_no, + status__in=(1, 2), # 1=待报价, 2=报价中 + quote_deadline__lt=now, + ).values("supplier_name", "contact_person", "contact_phone", "quote_deadline", "status") + + result = [] + for row in rows: + deadline_local = row["quote_deadline"] + if deadline_local and timezone.is_aware(deadline_local): + from django.utils.timezone import localtime + deadline_display = localtime(deadline_local).strftime("%Y-%m-%d %H:%M") + else: + deadline_display = str(deadline_local or "—") + + result.append({ + "supplier_name": row["supplier_name"] or "—", + "contact_person": row["contact_person"] or None, + "quote_deadline": deadline_display, + "status": row["status"], + }) + return result + + +def _send_deadline_expired_email(inquiry: Inquiry, expired_suppliers: List[Dict[str, Any]]) -> bool: + """ + 渲染并发送单封报价截止到期邮件(一个询价单合并所有供应商)。 + 复用 email_utils 的 send_email_notice 底层。 + """ + from apps.pisadmin.basicinfo.views.email_template import ( + TEMPLATE_QUOTE_DEADLINE_EXPIRED, + build_context_quote_deadline_expired, + render_email, + ) + from apps.pisadmin.basicinfo.views.email_utils import send_email_notice + from apps.pisadmin.basicinfo.models import EmailNotice + + to_list = _get_purchaser_emails(inquiry.inquiry_no) + if not to_list: + logger.warning("报价截止提醒无法找到采购负责人邮箱,inquiry_no=%s", inquiry.inquiry_no) + return False + + ctx = build_context_quote_deadline_expired(inquiry, expired_suppliers) + subject, body = render_email(TEMPLATE_QUOTE_DEADLINE_EXPIRED, ctx) + + notice = EmailNotice.objects.create( + subject=subject, + body=body, + to_emails=to_list, + cc_emails=[], + bcc_emails=[], + attachments=[], + biz_type="inquiry_quote_timeout", # 复用现有 biz_type 用于冷却期判断 + biz_id=inquiry.inquiry_no, + status="pending", + payload={ + "template_key": TEMPLATE_QUOTE_DEADLINE_EXPIRED, + "is_html": True, + "inquiry_no": ctx.get("inquiry_no"), + "supplier_count": ctx.get("supplier_count"), + }, + ) + notice.status = "sending" + notice.save(update_fields=["status", "update_datetime"]) + + success, detail = send_email_notice(notice) + notice.response = detail or {} + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + return success + + +@app.task(bind=True, max_retries=3, default_retry_delay=180) +def check_quote_deadline_expired(self) -> Dict[str, int]: + """ + Celery Beat 定时任务:轮询报价截止时间已到期的报价单, + 按询价单合并发送邮件通知采购负责人,每天最多一次。 + + 查询条件:status ∈ {1, 2}(待报价/报价中)且 quote_deadline < now + + Returns: {"checked": N, "sent": M, "skipped": K, "errors": E} + """ + now = timezone.now() + + # 1. 找出所有已到期但未报价的报价单 + expired_qs = QuotationMaster.objects.filter( + status__in=(1, 2), + quote_deadline__isnull=False, + quote_deadline__lt=now, + ).values_list("inquiry_no", flat=True).distinct() + + # 2. 按 inquiry_no 分组 + inquiry_nos = list(expired_qs) + + checked = len(inquiry_nos) + sent = 0 + skipped = 0 + errors = 0 + + for inq_no in inquiry_nos: + # 3. 检查24h冷却期 + if _is_in_cooldown(inq_no, cooldown_hours=24): + skipped += 1 + continue + + # 4. 获取 Inquiry 对象 + inquiry = Inquiry.objects.filter(inquiry_no=inq_no).first() + if not inquiry: + logger.warning("报价截止提醒未找到询价单,inquiry_no=%s", inq_no) + skipped += 1 + continue + + # 5. 构建到期供应商列表 + expired_suppliers = _build_expired_supplier_list(inq_no) + if not expired_suppliers: + skipped += 1 + continue + + # 6. 发送邮件 + try: + if _send_deadline_expired_email(inquiry, expired_suppliers): + sent += 1 + else: + errors += 1 + except Exception: + logger.exception("报价截止提醒邮件发送异常,inquiry_no=%s", inq_no) + errors += 1 + + logger.info( + "报价截止提醒任务完成: checked=%d, sent=%d, skipped=%d, errors=%d", + checked, sent, skipped, errors, + ) + return {"checked": checked, "sent": sent, "skipped": skipped, "errors": errors} diff --git a/backend/apps/pisadmin/miscprocurement/urls.py b/backend/apps/pisadmin/miscprocurement/urls.py new file mode 100644 index 0000000..e628d8e --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/urls.py @@ -0,0 +1,39 @@ +from rest_framework import routers + +from .views import ( + MiscMaterialViewSet, + MiscStationViewSet, + MiscPartViewSet, + MiscLowPriceHeaderViewSet, + MiscLowPriceDetailViewSet, + InquiryViewSet, + InquirySupplierViewSet, + InquiryAttachmentViewSet, + InquiryMaterialCostViewSet, + InquiryProcessCostViewSet, + InquiryOtherCostViewSet, + InquiryProfitCostViewSet, + InquiryRfqItemViewSet, + CostEstimateTemplateViewSet, + RFQOperationLogsViewSet, +) + +router = routers.SimpleRouter() +router.register(r'misc_materials', MiscMaterialViewSet) +router.register(r'misc_stations', MiscStationViewSet) +router.register(r'misc_parts', MiscPartViewSet) +router.register(r'inquiry', InquiryViewSet) +router.register(r'inquiry_supplier', InquirySupplierViewSet) +router.register(r'inquiry_attachment', InquiryAttachmentViewSet) +router.register(r'inquiry_material_cost', InquiryMaterialCostViewSet) +router.register(r'inquiry_process_cost', InquiryProcessCostViewSet) +router.register(r'inquiry_other_cost', InquiryOtherCostViewSet) +router.register(r'inquiry_profit_cost', InquiryProfitCostViewSet) +router.register(r'inquiry_rfq_item', InquiryRfqItemViewSet) +router.register(r'proc_low_price_header', MiscLowPriceHeaderViewSet) +router.register(r'proc_low_price_detail', MiscLowPriceDetailViewSet) +router.register(r'cost_template', CostEstimateTemplateViewSet) +router.register(r'rfq_operation_logs', RFQOperationLogsViewSet) + +urlpatterns = [] +urlpatterns += router.urls diff --git a/backend/apps/pisadmin/miscprocurement/views.py b/backend/apps/pisadmin/miscprocurement/views.py new file mode 100644 index 0000000..8917c2e --- /dev/null +++ b/backend/apps/pisadmin/miscprocurement/views.py @@ -0,0 +1,1833 @@ +import logging +from collections import defaultdict +from decimal import Decimal +from datetime import datetime +from typing import Optional + +from django.db import transaction +from django.db.models import Q +from django.utils import timezone +from rest_framework.decorators import action +from rest_framework import serializers + +from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse +from dvadmin.utils.viewset import CustomModelViewSet +from apps.pisadmin.basicinfo.models import Company, EmailNotice, SystemNoRule +from apps.pisadmin.basicinfo.views.email_template import ( + TEMPLATE_RFS_PUBLISH, + build_context_rfs_publish, + render_email, +) +from apps.pisadmin.basicinfo.views.email_utils import send_bids_publish_notice_to_supplier, send_email_notice + +from apps.pissupplier.models import ( + QuotationMaster, + QuotationMaterial, + QuotationProcess, + QuotationOther, + QuotationProfit, + QuotationItem, +) + +logger = logging.getLogger(__name__) + + +def _fmt_dt_for_inquiry_quotation(dt: Optional[datetime]) -> Optional[str]: + if not dt: + return None + if timezone.is_naive(dt): + dt = timezone.make_aware(dt, timezone.get_current_timezone()) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + +def _quotation_master_status_label(status) -> str: + """与 ``QuotationMaster.STATUS_CHOICES`` 一致的可读文案。""" + labels = {1: "待报价", 2: "报价中", 3: "已报价", 4: "已过期"} + try: + st = int(status) if status is not None else 1 + except (TypeError, ValueError): + return "—" + return labels.get(st, str(st)) + + +def build_invited_supplier_quotation_rows(inquiry) -> list: + """询价单发布后各受邀供应商报价单当前状态(与 ``QuotationMaster`` 一致,供操作日志嵌套表使用)。""" + inquiry_no = (inquiry.inquiry_no or "").strip() + if not inquiry_no: + return [] + + name_map: dict[str, str] = {} + for inv in InquirySupplier.objects.filter(inquiry_no=inquiry_no).only("supplier_code", "supplier_name"): + code = (inv.supplier_code or "").strip() + if not code: + continue + n = (inv.supplier_name or "").strip() + prev = name_map.get(code, "") + if len(n) > len(prev): + name_map[code] = n + + rows_out: list[dict] = [] + qms = QuotationMaster.objects.filter(inquiry_no=inquiry_no).order_by("supplier_code", "autoid") + for qm in qms: + code = (qm.supplier_code or "").strip() + full_name = (name_map.get(code) or (qm.supplier_name or "").strip() or code or "—").strip() + + st = int(qm.status or 1) + if st == 3 and qm.quotetime: + op_time = _fmt_dt_for_inquiry_quotation(qm.quotetime) + else: + op_time = _fmt_dt_for_inquiry_quotation(qm.creattime) if qm.creattime else None + + if st == 3: + op_user = (qm.quoteuser or "").strip() or "—" + else: + op_user = "—" + + rows_out.append( + { + "supplier_full_name": full_name, + "quotation_status": _quotation_master_status_label(qm.status), + "operation_time": op_time or "—", + "operator": op_user, + "quotation_no": (qm.quotation_no or "").strip(), + } + ) + return rows_out + + +from .models import ( + MiscProcurementMaterialInfo, + MiscProcurementStationInfo, + MiscProcMaterial, + MiscLowPriceHeader, + MiscLowPriceDetail, + MiscNegotiationRecords, + RFQOperationLogs, + Inquiry, + InquirySupplier, + InquiryAttachment, + InquiryMaterialCost, + InquiryProcessCost, + InquiryOtherCost, + InquiryProfitCost, + InquiryRfqItem, + CostEstimateTemplateHead, + CostEstimateTemplateBody, + MiscProcMaterialMinPrices, + MiscProcProcessingMinPrices, +) +from .serializers import ( + MiscMaterialSerializer, + MiscMaterialCreateUpdateSerializer, + MiscStationSerializer, + MiscStationCreateUpdateSerializer, + MiscPartSerializer, + MiscPartCreateUpdateSerializer, + MiscLowPriceHeaderSerializer, + MiscLowPriceDetailSerializer, + MiscNegotiationRecordsSerializer, + MiscNegotiationSaveSerializer, + RFQOperationLogsSerializer, + InquirySerializer, + InquirySupplierSerializer, + InquiryAttachmentSerializer, + InquiryMaterialCostSerializer, + InquiryProcessCostSerializer, + InquiryOtherCostSerializer, + InquiryProfitCostSerializer, + InquiryRfqItemSerializer, + CostEstimateTemplateHeadSerializer, + CostEstimateTemplateNewVersionSerializer, + create_cost_template_new_version, + MiscProcMaterialMinPricesSerializer, + MiscProcProcessingMinPricesSerializer, +) + + +def _negotiation_totals_from_quotation_item(quotation_no: str, part_id: str): + """从杂采报价单上阶物料明细取议价前含税/不含税总价(与 part_id 匹配行)。""" + if not quotation_no or not part_id: + return None, None + item = ( + QuotationItem.objects.filter(quotation_no=quotation_no, part_id=part_id) + .order_by("autoid") + .first() + ) + if not item: + return None, None + return item.total_price_excl_tax, item.total_price_incl_tax + + +def _safe_decimal(v): + if v is None: + return None + try: + return Decimal(str(v)) + except Exception: + return None + + +def _get_earliest_quotation(quotations: list) -> tuple: + """从报价单列表中获取最早提交的报价单(按 quotetime 或 creattime 排序)""" + if not quotations: + return None, None + + earliest = None + earliest_time = None + earliest_qn = None + + for q in quotations: + q_time = getattr(q, 'quotetime', None) or getattr(q, 'creattime', None) + qn = getattr(q, 'quotation_no', None) or getattr(q, 'quotationNo', None) + + if q_time is None: + continue + + if earliest is None or q_time < earliest_time: + earliest = q + earliest_time = q_time + earliest_qn = qn + + return earliest, earliest_qn + + +def _resolve_inquiry_primary_part_id(inquiry: Inquiry) -> str: + """与采购端比价弹窗一致:上阶物料首行料号。""" + first = InquiryRfqItem.objects.filter(inquiry_no=inquiry).order_by("id").first() + if not first: + return "" + return (first.part_id or "").strip() + + +def _clip_price_str(value, max_len: int = 10) -> str: + s = str(value).strip() if value is not None else "" + return s[:max_len] + + +def _clip_field(value, max_len: int) -> str: + return str(value or "").strip()[:max_len] + + +def _norm_low_price_src(s: str, max_len: int = 200) -> str: + return (s or "").strip()[:max_len] + + +def _material_cost_header_souce_no(weight_src: Optional[str], unit_src: Optional[str]) -> str: + """主表材料行:聚合次表来源;W=最低重量对应报价单号,U=最低单价对应报价单号或杂采厂区代码。""" + parts = [] + if weight_src: + parts.append(f"W:{_norm_low_price_src(weight_src)}") + if unit_src: + parts.append(f"U:{_norm_low_price_src(unit_src)}") + return _norm_low_price_src(";".join(parts)) + + +def _rfq_qty_decimal(inquiry: Inquiry, pid: str) -> Optional[Decimal]: + """上阶物料需求数量;无则返回 None。""" + pid = (pid or "").strip() + if not pid: + return None + it = ( + InquiryRfqItem.objects.filter(inquiry_no=inquiry, part_id=pid) + .order_by("id") + .first() + ) + if it is None or it.qty is None: + return None + try: + return Decimal(str(it.qty)) + except Exception: + return None + + +def _sync_misc_low_price_records(inquiry: Inquiry, part_id: str) -> None: + """ + 制程最低价落库(与比价展开明细一致): + - 先比较所有报价单的最低价。 + - 如果**所有报价单最低价相等**,则取**最早提交报价单**的最低价。 + - 如果**最低价不一致**,则取**所有报价单中的最小值**。 + - 材料:按材质分组,每组单独计算最低重量/单价,再汇总所有材质的材料费用总和。 + - 加工:每份报价单加工费合计后取最小值。 + - 材料/加工最低价仅更新 `MiscLowPriceHeader`/`MiscLowPriceDetail` 记录表, + `MiscProcMaterialMinPrices`/`MiscProcProcessingMinPrices` 信息表仅在核价完成后更新(暂不实现)。 + """ + inquiry_no = (inquiry.inquiry_no or "").strip() + if not inquiry_no: + return + + # 获取所有报价单(排除已过期 status=4) + qm_qs = QuotationMaster.objects.filter(inquiry_no=inquiry_no).exclude(status=4) + qn_list = list(qm_qs.values_list("quotation_no", flat=True)) + souce_ref = inquiry_no[:20] + + # 获取所有报价单详情,用于判断最早提交报价单 + all_quotations = list(qm_qs.order_by("creattime", "quotetime")) + + raw_parts = [ + str(x).strip() + for x in InquiryRfqItem.objects.filter(inquiry_no=inquiry) + .values_list("part_id", flat=True) + .distinct() + if x is not None and str(x).strip() != "" + ] + fallback = (part_id or "").strip() + part_ids = raw_parts if raw_parts else ([fallback] if fallback else []) + if not part_ids: + return + + for pid in part_ids: + MiscLowPriceHeader.objects.filter(inquiry_no=inquiry_no, part_id=pid).delete() + MiscLowPriceDetail.objects.filter(inquiry_no=inquiry_no, part_id=pid).delete() + + # 获取该料号的所有报价单(用于判断最早提交报价单) + pid_quotations = [q for q in all_quotations if q.quotation_no in qn_list] + + # —— 材料:按材质规格分组,每组次表两行(重量、单价);主表 = 所有分组的(最低重量×最低单价×数量)之和。 + # 如果所有报价单材料费用相等,则取最早提交报价单;否则取最小值。 + # FK 须用报价单单号列表:quotation_no__in=QuerySet(QuotationMaster) 会按主键匹配,导致材料行查不到。 + materials = QuotationMaterial.objects.filter(quotation_no__in=qn_list, part_id=pid) + + # 按材质规格分组收集候选值 + from collections import defaultdict + spec_weight_candidates: dict[str, list[tuple[Decimal, str]]] = defaultdict(list) + spec_unit_candidates: dict[str, list[tuple[Decimal, str]]] = defaultdict(list) + spec_quote_data: dict[str, dict[str, dict]] = {} + + for m in materials: + qn_src = getattr(m, "quotation_no_id", None) or "" + qn_src = str(qn_src).strip() + qn = getattr(m, "quotation_no_id", None) or qn_src + spec = (getattr(m, "material_spec", None) or "").strip() or "-" + + # 记录每个材质规格下每个报价单的数据,用于判断是否所有报价单相等 + if spec not in spec_quote_data: + spec_quote_data[spec] = {} + if qn not in spec_quote_data[spec]: + spec_quote_data[spec][qn] = {} + + if m.weight is not None: + try: + w = Decimal(str(m.weight)) + spec_weight_candidates[spec].append((w, qn_src)) + spec_quote_data[spec][qn]['weight'] = w + except Exception: + pass + if m.unit_price is not None: + try: + up = Decimal(str(m.unit_price)) + spec_unit_candidates[spec].append((up, qn_src)) + spec_quote_data[spec][qn]['unit_price'] = up + except Exception: + pass + if m.material_cost is not None: + try: + mc = Decimal(str(m.material_cost)) + spec_quote_data[spec][qn]['material_cost'] = mc + except Exception: + pass + + # 杂采材料信息:按「材质」与报价 material_spec 对齐;不得把其它材质的最低单价并入本组 + misc_by_spec: dict[str, list[tuple[Decimal, str]]] = defaultdict(list) + for mi in MiscProcurementMaterialInfo.objects.filter(status=1).only("price", "factory", "materialtype"): + if mi.price is None: + continue + mat = (getattr(mi, "materialtype", None) or "").strip() + if not mat: + continue + try: + up = Decimal(str(mi.price)) + fac = (getattr(mi, "factory", None) or "").strip() or "MISC" + misc_by_spec[mat].append((up, fac)) + except Exception: + pass + + qty_dec = _rfq_qty_decimal(inquiry, pid) + total_material_low = Decimal("0") + all_src_w_list: list[str] = [] + all_src_up_list: list[str] = [] + + # 获取所有材质规格(有重量或单价数据的) + all_specs = set(spec_weight_candidates.keys()) | set(spec_unit_candidates.keys()) + + for spec in all_specs: + weight_candidates = spec_weight_candidates.get(spec, []) + unit_candidates = spec_unit_candidates.get(spec, []) + + # 仅与同 material_spec 一致的杂采材质行参与该组最低价 + unit_candidates = unit_candidates + list(misc_by_spec.get(spec, [])) + + # 按材质分组判断所有报价单是否相等 + spec_quote_values = spec_quote_data.get(spec, {}) + all_quotes_equal = True + first_value = None + + for qn, data in spec_quote_values.items(): + # 检查重量、单价、材料费用是否都相等 + current_values = [] + if 'weight' in data: + current_values.append(data['weight']) + if 'unit_price' in data: + current_values.append(data['unit_price']) + if 'material_cost' in data: + current_values.append(data['material_cost']) + + if not current_values: + continue + + if first_value is None: + first_value = current_values[0] + else: + for val in current_values: + if abs(val - first_value) > Decimal('0.0001'): + all_quotes_equal = False + break + if not all_quotes_equal: + break + + min_w_row = min(weight_candidates, key=lambda t: (t[0], t[1])) if weight_candidates else None + min_up_row = min(unit_candidates, key=lambda t: (t[0], t[1])) if unit_candidates else None + min_w = min_w_row[0] if min_w_row else None + min_up = min_up_row[0] if min_up_row else None + src_w = min_w_row[1] if min_w_row else None + src_up = min_up_row[1] if min_up_row else None + + # 如果该材质分组的所有报价单相等,则取最早提交报价单的数据 + if all_quotes_equal and len(pid_quotations) > 0 and min_w is not None and min_up is not None: + earliest, earliest_qn = _get_earliest_quotation(pid_quotations) + if earliest_qn: + # 找到最早报价单对应的具体数据 + earliest_data = spec_quote_data.get(spec, {}).get(earliest_qn) + if earliest_data: + if 'weight' in earliest_data: + min_w = earliest_data['weight'] + if 'unit_price' in earliest_data: + min_up = earliest_data['unit_price'] + src_w = earliest_qn + src_up = earliest_qn + + # 判断是否来自杂采材料信息(本材质分组) + is_misc_src = False + if min_up is not None and misc_by_spec.get(spec): + for misc_up, misc_fac in misc_by_spec[spec]: + if abs(min_up - misc_up) < Decimal("0.0001"): + is_misc_src = True + break + + # 写入次表:最低重量 + if min_w is not None: + MiscLowPriceDetail.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + cost_type="1", + material_spec=spec[:50], + item_no="1", + value=_clip_price_str(min_w), + souce_no=_norm_low_price_src((src_w or souce_ref)), + ) + if src_w: + all_src_w_list.append(str(src_w)) + + # 写入次表:最低单价 + if min_up is not None: + MiscLowPriceDetail.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + cost_type="1", + material_spec=spec[:50], + item_no="2", + value=_clip_price_str(min_up), + souce_no=_norm_low_price_src((src_up or souce_ref)) if not is_misc_src else _norm_low_price_src(str(src_up or souce_ref)), + ) + if src_up: + all_src_up_list.append(str(src_up)) + + # 累加到总材料费用 + if min_w is not None and min_up is not None and qty_dec is not None and qty_dec > 0: + spec_material_low = min_w * min_up * qty_dec + total_material_low += spec_material_low + + # 写入主表材料行:所有分组的材料费用总和 + if total_material_low > 0: + # 聚合来源:W:所有重量来源; U:所有单价来源 + aggregated_src_w = ",".join(sorted(set(all_src_w_list))) if all_src_w_list else None + aggregated_src_up = ",".join(sorted(set(all_src_up_list))) if all_src_up_list else None + MiscLowPriceHeader.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + souce_no=_material_cost_header_souce_no(aggregated_src_w, aggregated_src_up) or None, + cost_type="1", + item_no="材料", + min_price=_clip_price_str(total_material_low), + ) + + # —— 加工:仅主表一行,无次表、不按工站。每份报价单对该料号加工费合计(空/缺省按 0),再取最小; + # 如果所有报价单加工费合计相等,则取最早提交报价单;否则取最小值。 + proc_totals: list[tuple[Decimal, str]] = [] + for qn in qn_list: + total = Decimal("0") + for p in QuotationProcess.objects.filter(quotation_no=qn, part_id=pid): + if p.process_price is not None: + try: + total += Decimal(str(p.process_price)) + except Exception: + pass + proc_totals.append((total, str(qn).strip())) + + if proc_totals: + # 检查所有加工费是否相等 + all_equal = all(abs(t[0] - proc_totals[0][0]) < Decimal('0.0001') for t in proc_totals) + + if all_equal and len(pid_quotations) > 0: + # 所有报价加工费相等,取最早提交报价单 + earliest, earliest_qn = _get_earliest_quotation(pid_quotations) + if earliest_qn: + best_total = proc_totals[0][0] # 任意一个都相等 + best_qn = earliest_qn + else: + best_total, best_qn = min(proc_totals, key=lambda t: (t[0], t[1])) + else: + # 加工费不一致或无报价单,取最小值 + best_total, best_qn = min(proc_totals, key=lambda t: (t[0], t[1])) + else: + best_total = Decimal("0") + best_qn = "" + + MiscLowPriceHeader.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + souce_no=_norm_low_price_src(best_qn) if best_qn else (souce_ref or None), + cost_type="2", + item_no="加工", + min_price=_clip_price_str(best_total), + ) + + # —— 包装费 / 运输费(报价单单号列表;兼容 part_id 大小写;无子表时回退上阶物料 total_other_expense)—— + others = QuotationOther.objects.filter(quotation_no__in=qn_list).filter( + Q(part_id=pid) | Q(part_id__iexact=(pid or "").strip()) + ) + pkg_vals = [] + tr_vals = [] + for o in others: + if o.packaging_cost is not None: + try: + pkg_vals.append(Decimal(str(o.packaging_cost))) + except Exception: + pass + if o.transportation_cost is not None: + try: + tr_vals.append(Decimal(str(o.transportation_cost))) + except Exception: + pass + if not pkg_vals and not tr_vals: + for qn in qn_list: + it = ( + QuotationItem.objects.filter(quotation_no=qn, part_id=pid).order_by("autoid").first() + or QuotationItem.objects.filter(quotation_no=qn).order_by("autoid").first() + ) + if it is None or it.total_other_expense is None: + continue + try: + v = Decimal(str(it.total_other_expense)) + except Exception: + continue + pkg_vals.append(v) + tr_vals.append(v) + + if pkg_vals: + MiscLowPriceHeader.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + souce_no=souce_ref or None, + cost_type="3", + item_no="包装费", + min_price=_clip_price_str(min(pkg_vals)), + ) + if tr_vals: + MiscLowPriceHeader.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + souce_no=souce_ref or None, + cost_type="4", + item_no="运输费", + min_price=_clip_price_str(min(tr_vals)), + ) + + # —— 利润率 cost_type=6(杂采无管销研 cost_type=5,不落库)—— + pr_vals = [] + for qn in qn_list: + it = QuotationItem.objects.filter(quotation_no=qn, part_id=pid).order_by("autoid").first() + if it is None: + it = QuotationItem.objects.filter(quotation_no=qn).order_by("autoid").first() + if it is not None and it.profit_rate is not None and str(it.profit_rate).strip() != "": + v = _safe_decimal(it.profit_rate) + if v is not None: + pr_vals.append(v) + continue + pf = QuotationProfit.objects.filter(quotation_no=qn, part_id=pid).order_by("autoid").first() + if pf is not None and pf.profit_rate is not None: + v = _safe_decimal(pf.profit_rate) + if v is not None: + pr_vals.append(v) + if pr_vals: + MiscLowPriceHeader.objects.create( + inquiry_no=inquiry_no, + part_id=pid, + souce_no=souce_ref or None, + cost_type="6", + item_no="利润率", + min_price=_clip_price_str(min(pr_vals)), + ) + + +class MiscMaterialViewSet(CustomModelViewSet): + queryset = MiscProcurementMaterialInfo.objects.all() + serializer_class = MiscMaterialSerializer + create_serializer_class = MiscMaterialCreateUpdateSerializer + update_serializer_class = MiscMaterialCreateUpdateSerializer + search_fields = ["materialtype", "factory"] + ordering = ["-create_datetime"] + # 与询价单 `Inquiry.company_code` 一致,须精确匹配 `factory`,避免 sz_avc 与 sz_avcx 串数据 + filter_fields = ("factory", "status", "materialtype") + + +class MiscStationViewSet(CustomModelViewSet): + queryset = MiscProcurementStationInfo.objects.all() + serializer_class = MiscStationSerializer + create_serializer_class = MiscStationCreateUpdateSerializer + update_serializer_class = MiscStationCreateUpdateSerializer + search_fields = ["stationname", "stationcode", "company_code"] + ordering = ["-create_datetime"] + filter_fields = ("company_code", "status", "stationcode", "stationname") + + +class MiscPartViewSet(CustomModelViewSet): + queryset = MiscProcMaterial.objects.all() + serializer_class = MiscPartSerializer + create_serializer_class = MiscPartCreateUpdateSerializer + update_serializer_class = MiscPartCreateUpdateSerializer + search_fields = ["partid", "partid_name", "company_code"] + ordering = ["-create_datetime"] + + +class CostEstimateTemplateViewSet(CustomModelViewSet): + """成本估算模板主表;status:0 未确认 / 1 已确认 / 2 作废(列表默认不含作废)。""" + + queryset = CostEstimateTemplateHead.objects.all() + serializer_class = CostEstimateTemplateHeadSerializer + filter_fields = ("template_no", "template_name", "procurement_category", "is_bom", "acti", "version", "status") + search_fields = ("template_no", "template_name", "template_desc") + ordering = ("-update_time", "-id") + + # 与 models.CostEstimateTemplateHead.STATUS_CHOICES 中「作废」取值一致 + TEMPLATE_STATUS_VOID = 2 + TEMPLATE_STATUS_DRAFT = 0 + TEMPLATE_STATUS_CONFIRMED = 1 + + @staticmethod + def _head_status_value(instance) -> int: + # status 为 0(未确认)时不能使用 (value or -1),否则 0 会被当成 falsy 变成 -1 + v = getattr(instance, "status", None) + if v is None: + return -1 + try: + return int(v) + except (TypeError, ValueError): + return -1 + + def get_queryset(self): + qs = super().get_queryset() + # 仅列表默认隐藏作废;retrieve/update 等需能按 id 加载作废行(否则筛选作废后无法查看详情) + if getattr(self, "action", None) != "list": + return qs + params = getattr(self.request, "query_params", getattr(self.request, "GET", {})) + status_raw = params.get("status") + # 列表默认不展示作废;查询区显式选「状态=作废」时传 status=2,不过滤以便列出作废行 + if status_raw is not None and str(status_raw).strip() != "": + try: + st = int(status_raw) + except (TypeError, ValueError): + return qs.exclude(status=self.TEMPLATE_STATUS_VOID) + if st == self.TEMPLATE_STATUS_VOID: + return qs + return qs.exclude(status=self.TEMPLATE_STATUS_VOID) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + def update(self, request, *args, **kwargs): + instance = self.get_object() + st = self._head_status_value(instance) + if st == self.TEMPLATE_STATUS_VOID: + return ErrorResponse(msg="模板已作废,不可修改") + if st == self.TEMPLATE_STATUS_CONFIRMED: + return ErrorResponse(msg="已确认模板不可修改") + return super().update(request, *args, **kwargs) + + def partial_update(self, request, *args, **kwargs): + instance = self.get_object() + st = self._head_status_value(instance) + if st == self.TEMPLATE_STATUS_VOID: + return ErrorResponse(msg="模板已作废,不可修改") + if st == self.TEMPLATE_STATUS_CONFIRMED: + return ErrorResponse(msg="已确认模板不可修改") + return super().partial_update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + return ErrorResponse(msg="成本模板不允许删除") + + @action(methods=['delete'], detail=False) + def multiple_delete(self, request, *args, **kwargs): + return ErrorResponse(msg="成本模板不允许删除") + + @action(methods=['post'], detail=True) + def confirm(self, request, *args, **kwargs): + """未确认(0) → 已确认(1)。同模板编号下 version 低于当前行的非作废主表行一律改为作废(2)。""" + instance = self.get_object() + cur = self._head_status_value(instance) + if cur == self.TEMPLATE_STATUS_VOID: + return ErrorResponse(msg="作废模板不可确认") + if cur != self.TEMPLATE_STATUS_DRAFT: + return ErrorResponse(msg="仅未确认状态的模板可确认") + now = timezone.now() + username = getattr(getattr(request, "user", None), "username", None) or "" + uname = str(username)[:20] if username else "" + + with transaction.atomic(): + qs_old = CostEstimateTemplateHead.objects.filter( + template_no=instance.template_no, + version__lt=instance.version, + ).exclude(status=self.TEMPLATE_STATUS_VOID) + voided_count = qs_old.count() + upd = {"status": self.TEMPLATE_STATUS_VOID, "update_time": now} + if uname: + upd["update_user"] = uname + qs_old.update(**upd) + + instance.status = self.TEMPLATE_STATUS_CONFIRMED + instance.update_time = now + if uname: + instance.update_user = uname + instance.save(update_fields=["status", "update_time", "update_user"]) + + serializer = self.get_serializer(instance) + msg = "确认成功,已作废旧版本" if voided_count else "确认成功" + return DetailResponse(data=serializer.data, msg=msg) + + @action(methods=["post"], detail=True, url_path="new_version") + def new_version(self, request, *args, **kwargs): + """已确认模板派生新版本:同 template_no,version=max+1,status=0;请求体校验见 CostEstimateTemplateNewVersionSerializer。""" + source = self.get_object() + if self._head_status_value(source) != self.TEMPLATE_STATUS_CONFIRMED: + return ErrorResponse(msg="仅已确认模板可创建新版本") + sz = CostEstimateTemplateNewVersionSerializer(data=request.data, context={"request": request}) + if not sz.is_valid(): + return ErrorResponse(msg="参数校验失败", data=sz.errors) + try: + head = create_cost_template_new_version(source, sz.validated_data, request) + except serializers.ValidationError as e: + detail = getattr(e, "detail", None) + if isinstance(detail, dict): + first_msg = None + for v in detail.values(): + if isinstance(v, list) and v: + first_msg = str(v[0]) + break + if isinstance(v, str) and v.strip(): + first_msg = v.strip() + break + return ErrorResponse(msg=first_msg or "创建新版本失败", data=detail) + return ErrorResponse(msg=str(detail or e)) + out = CostEstimateTemplateHeadSerializer(head, context={"request": request}) + return DetailResponse(data=out.data, msg="新版本创建成功") + + +class InquiryViewSet(CustomModelViewSet): + """ + 杂采询价单 CRUD/发布等。 + + 与供应商报价单联动:当 `pissupplier.QuotationMasterViewSet.sync_expired` 将超时报价置为已过期后, + 若该询价下已无待报价/报价中的报价单,且主表状态仍为「发布」(3) 或「报价中」(4), + 会由 `Inquiry.sync_to_quote_closed_when_no_open_quotations` 将询价单置为「报价结束」(5)。 + """ + + queryset = Inquiry.objects.all().prefetch_related( + "suppliers", + "attachments", + "material_costs", + "process_costs", + "other_costs", + "profit_costs", + "rfq_items", + ) + serializer_class = InquirySerializer + filter_fields = ( + "inquiry_no", + "title", + "purchase_type", + "template", + "template_version", + "status", + "buyer", + "buying_method", + ) + search_fields = ("inquiry_no", "title", "material_type", "buyer", "remark") + ordering = ("-update_datetime",) + + STATUS_OPEN = 1 + STATUS_CONFIRMED = 2 + STATUS_PUBLISHED = 3 + STATUS_QUOTING = 4 + STATUS_QUOTE_ENDED = 5 + STATUS_BARGaining = 6 + STATUS_NEGOTIATED = 7 + STATUS_PRICE_REVIEW = 7 + STATUS_APPROVED = 8 + STATUS_AWARDED = 9 + STATUS_VOID = 0 + TEMPLATE_PREFILL_FIELD_MAP = { + "1": { + "material": "material_spec", + "materialspec": "material_spec", + "length": "length", + "width": "width", + "height": "height", + "specificgravity": "specific_gravity", + "qty": "qty", + "unitprice": "unit_price", + "materialcost": "material_cost", + "weight": "weight", + "remark": "remark", + }, + "2": { + "processstation": "process_station", + "unit": "unit", + "unitrate": "unit_rate", + "processqty": "process_qty", + "processprice": "process_price", + "remark": "remark", + }, + "3": { + "packagingcost": "packaging_cost", + "transportationcost": "transportation_cost", + }, + "5": { + "profitrate": "profit_rate", + }, + "6": { + "taxrate": "tax_rate", + }, + "7": { + "partno": "part_id", + "desc": "product_name", + "productname": "product_name", + "unit": "unit", + "qty": "qty", + "price": "unit_price", + "unitprice": "unit_price", + "amount": "product_cost", + "totalmaterialcost": "total_material_cost", + "totalprocessingcost": "total_processing_cost", + "totalotherexpense": "total_other_expense", + "totalopexamt": "total_opex_amt", + "profitrate": "profit_rate", + "taxrate": "tax_rate", + "totalpriceexcltax": "total_price_excl_tax", + "totalpriceincltax": "total_price_incl_tax", + }, + } + + @staticmethod + def _clip(value, max_length): + text = str(value or "").strip() + return text[:max_length] + + @staticmethod + def _normalize_template_key(value): + text = str(value or "").strip().lower() + return "".join(ch for ch in text if ch.isalnum()) + + def _get_request_username(self) -> str: + user = getattr(self.request, "user", None) + if not user: + return "" + return ( + getattr(user, "username", "") + or getattr(user, "name", "") + or getattr(user, "nick_name", "") + or getattr(user, "nickname", "") + or "" + ) + + def _ensure_editable(self, instance): + if int(getattr(instance, "status", self.STATUS_OPEN) or self.STATUS_OPEN) != self.STATUS_OPEN: + return ErrorResponse(msg="当前询价单状态仅允许查看,不允许编辑或删除;如需修改请先还原为“开立”") + return None + + def _save_status( + self, + instance, + *, + status, + confirm_user=None, + confirm_time=None, + release_user=None, + release_time=None, + comparison_user=None, + comparison_time=None, + operation_type=None, + operation_desc=None, + ): + old_status = int(instance.status if instance.status is not None else 0) + current_time = timezone.now() + current_user = self._get_request_username() or getattr(instance, "update_user", None) + instance.status = status + instance.confirm_user = confirm_user + instance.confirm_time = confirm_time + instance.release_user = release_user + instance.release_time = release_time + if comparison_user is not None: + instance.comparison_user = comparison_user + if comparison_time is not None: + instance.comparison_time = comparison_time + instance.update_user = current_user + instance.update_time = current_time + instance.save( + update_fields=[ + "status", + "confirm_user", + "confirm_time", + "release_user", + "release_time", + "comparison_user", + "comparison_time", + "update_user", + "update_time", + ] + ) + new_status = int(status) + if operation_type is not None and old_status != new_status: + RFQOperationLogs.try_append( + inquiry_no=instance.inquiry_no, + purchase_type=int(instance.purchase_type), + operation_type=operation_type, + operation_user=current_user or None, + quotation_no="-", + per_status=old_status, + cur_status=new_status, + operation_desc=operation_desc, + ) + serializer = self.get_serializer(instance) + return DetailResponse(data=serializer.data, msg="状态更新成功") + + def _generate_quotation_numbers(self, count: int, inquiry: Inquiry): + """ + 杂采报价单号:复用 `SystemNoRule`(rule_code=miscQTS),与询价单 miscRFS 同源取号逻辑。 + company_code 优先询价单字段,否则「通用」厂区。 + """ + if count <= 0: + return [] + company_code = (getattr(inquiry, "company_code", None) or "").strip() or SystemNoRule.DEFAULT_SYSTEM_NO_COMPANY_CODE + username = self._get_request_username() or None + return SystemNoRule.allocate_system_numbers( + company_code, + "miscQTS", + count, + username=username, + now=timezone.now(), + ) + + def _get_template_prefill_fields(self, template_no, template_version=None): + """ + 根据询价单关联的成本模板(Inquiry.template = template_no,Inquiry.template_version = 主表 version), + 找出「发布生成报价单时」允许从询价单带入到报价子表的字段名集合。 + + 满足以下任一条件时,对应映射字段进入集合(从询价单带入报价子表),否则为空值(见 _pick_prefill_value): + - is_computed == 1(系统自动计算/带出) + - supplier_required == 1(带出不可修改)或 2(带出可修改) + + template_version 为空时兼容旧数据:取该编号下「已确认」主表的最高 version。 + """ + if not template_no: + return {} + + tn = str(template_no).strip() + base_qs = CostEstimateTemplateHead.objects.filter(template_no=tn, status=1) + head = None + if template_version is not None and template_version != "": + try: + ver = int(template_version) + except (TypeError, ValueError): + ver = None + if ver is not None: + head = base_qs.filter(version=ver).first() + if head is None: + head = base_qs.order_by("-version", "-id").first() + if not head: + return {} + + prefill_fields = {} + queryset = CostEstimateTemplateBody.objects.filter( + template_no=head.template_no, + version=head.version, + ).filter(Q(is_computed=1) | Q(supplier_required__in=(1, 2))).values( + "cost_category", "item_no", "item_name_cn" + ) + + for row in queryset: + cost_category = str(row.get("cost_category") or "").strip() + mapping = self.TEMPLATE_PREFILL_FIELD_MAP.get(cost_category, {}) + raw_keys = [row.get("item_no"), row.get("item_name_cn")] + for raw_key in raw_keys: + normalized = self._normalize_template_key(raw_key) + target_field = mapping.get(normalized) + if target_field: + prefill_fields.setdefault(cost_category, set()).add(target_field) + return prefill_fields + + @staticmethod + def _pick_prefill_value(prefill_fields, cost_category, field_name, source_value, empty_default=None): + category_fields = prefill_fields.get(str(cost_category), set()) + if field_name in category_fields: + return source_value + return empty_default + + @staticmethod + def _resolve_purchaser_company_name(inquiry: Inquiry) -> str: + code = (getattr(inquiry, "company_code", None) or "").strip() + if code: + row = Company.objects.filter(company_code=code).only("company_name", "company_short_name").first() + if row: + return (row.company_name or row.company_short_name or code).strip() + return code + return "" + + def _group_inquiry_suppliers(self, inquiry: Inquiry): + """ + 按询价单供应商子表 `InquirySupplier` 汇总:同一 `supplier_code`(空代码时用行级占位)合并多料号 `part_id`, + 供 `_create_supplier_quotations` 为每个供应商生成一份 `QuotationMaster`。 + 返回条目的 supplier_code / supplier_name / 联系人字段均来自子表行数据(截断至报价主表字段长度)。 + 采购端保存时,联系人/邮箱/电话由前端在「供应商用户信息」表中按 supplier_id 选择后写入子表,此处不再与主档做 ORM 关联校验。 + """ + supplier_map = {} + for row in inquiry.suppliers.all(): + clipped_code = self._clip(row.supplier_code, 20) + # 与 key 一致:无代码时用行级占位,保证写入 QuotationMaster.supplier_code 非空(序列化必填) + key = clipped_code or f"SUP-{row.id}" + current = supplier_map.setdefault( + key, + { + "supplier_code": clipped_code or key, + "supplier_name": self._clip(row.supplier_name, 20), + "contact_person": self._clip(row.contact_person, 20), + "contact_phone": self._clip(row.contact_phone, 20), + "contact_email": self._clip(row.contact_email, 20), + "part_ids": set(), + }, + ) + current["part_ids"].add(row.part_id) + # 合并多行时:若后续行补全了真实供应商代码,覆盖 SUP-* 占位 + if clipped_code and ( + not current.get("supplier_code") + or str(current["supplier_code"]).startswith("SUP-") + ): + current["supplier_code"] = clipped_code + if not current["supplier_name"] and row.supplier_name: + current["supplier_name"] = self._clip(row.supplier_name, 20) + if not current["contact_person"] and row.contact_person: + current["contact_person"] = self._clip(row.contact_person, 20) + if not current["contact_phone"] and row.contact_phone: + current["contact_phone"] = self._clip(row.contact_phone, 20) + if not current["contact_email"] and row.contact_email: + current["contact_email"] = self._clip(row.contact_email, 20) + groups = list(supplier_map.values()) + for g in groups: + # 子表未填名称时,用已确定的代码占位,避免报价主表 supplier_name 为空串 + if not (g.get("supplier_name") or "").strip(): + g["supplier_name"] = self._clip(g.get("supplier_code") or "", 20) + return groups + + def _ensure_inquiry_has_supplier_groups(self, inquiry: Inquiry, *, empty_message: str): + """ + 仅在「确认」时调用:供应商子表经 `_group_inquiry_suppliers` 汇总后须非空。 + 「发布」仅允许在已确认后进行,不再重复校验名单(与业务约定一致)。 + """ + groups = self._group_inquiry_suppliers(inquiry) + if not groups: + raise serializers.ValidationError(empty_message) + return groups + + def _create_supplier_quotations(self, inquiry: Inquiry): + """ + 询价单发布:按供应商子表汇总结果,为每个供应商创建一张 `QuotationMaster`(及子表明细), + 主表 `supplier_code`、`supplier_name` 与联系人信息与 `InquirySupplier` 对应数据一致(见 `_group_inquiry_suppliers`)。 + 名单是否在空已在确认环节校验,此处不再校验。 + """ + supplier_groups = self._group_inquiry_suppliers(inquiry) + + QuotationMaster.objects.filter(inquiry_no=inquiry.inquiry_no).delete() + + quotation_numbers = self._generate_quotation_numbers(len(supplier_groups), inquiry) + prefill_fields = self._get_template_prefill_fields( + getattr(inquiry, "template", None), + getattr(inquiry, "template_version", None), + ) + current_user = self._clip( + self._get_request_username() or getattr(inquiry, "release_user", None), + 20, + ) or None + current_time = timezone.now() + # 招标(2):供应商端报价单沿用「投标截止时间」作为有效报价截止;询价(1)用主表 quote_deadline。 + quote_deadline = inquiry.quote_deadline + if int(getattr(inquiry, "buying_method", 1) or 1) == 2: + quote_deadline = getattr(inquiry, "bid_end_time", None) or quote_deadline + + inquiry_materials = list(inquiry.material_costs.all()) + inquiry_processes = list(inquiry.process_costs.all()) + inquiry_others = list(inquiry.other_costs.all()) + inquiry_profits = list(inquiry.profit_costs.all()) + inquiry_items = list(inquiry.rfq_items.all()) + + material_bulk = [] + process_bulk = [] + other_bulk = [] + profit_bulk = [] + item_bulk = [] + + for quotation_no, supplier in zip(quotation_numbers, supplier_groups): + # 主表供应商字段与 InquirySupplier 子表一致(已由 _group_inquiry_suppliers 从子表汇总) + quotation = QuotationMaster.objects.create( + quotation_no=quotation_no, + inquiry_no=inquiry.inquiry_no, + supplier_code=supplier["supplier_code"], + supplier_name=supplier["supplier_name"], + contact_person=supplier["contact_person"] or None, + contact_phone=supplier["contact_phone"] or None, + contact_email=supplier["contact_email"] or None, + quote_deadline=quote_deadline, + buying_method=getattr(inquiry, "buying_method", None), + bid_start_time=getattr(inquiry, "bid_start_time", None), + bid_end_time=getattr(inquiry, "bid_end_time", None), + delivery_days=getattr(inquiry, "lead_time_days", None), + payment_method=getattr(inquiry, "payment_method", None), + status=1, + is_awarded=0, + createuser=current_user, + creattime=current_time, + remark=None, + ) + part_ids = supplier["part_ids"] + + for row in inquiry_materials: + if row.part_id in part_ids: + material_spec_src = self._pick_prefill_value( + prefill_fields, "1", "material_spec", row.material_spec, empty_default="" + ) + material_bulk.append( + QuotationMaterial( + quotation_no=quotation, + part_id=row.part_id, + material_spec=self._clip(material_spec_src or "", 20), + length=self._pick_prefill_value(prefill_fields, "1", "length", row.length), + width=self._pick_prefill_value(prefill_fields, "1", "width", row.width), + height=self._pick_prefill_value(prefill_fields, "1", "height", row.height), + unit_price=self._pick_prefill_value(prefill_fields, "1", "unit_price", row.unit_price), + qty=self._pick_prefill_value(prefill_fields, "1", "qty", row.qty), + specific_gravity=self._pick_prefill_value( + prefill_fields, "1", "specific_gravity", row.specific_gravity + ), + material_cost=self._pick_prefill_value(prefill_fields, "1", "material_cost", row.material_cost), + weight=self._pick_prefill_value(prefill_fields, "1", "weight", row.weight), + remark=self._pick_prefill_value(prefill_fields, "1", "remark", row.remark), + option_json=row.option_json if prefill_fields.get("1") else None, + ) + ) + + for row in inquiry_processes: + if row.part_id in part_ids: + process_station_src = self._pick_prefill_value( + prefill_fields, "2", "process_station", row.process_station + ) + process_bulk.append( + QuotationProcess( + quotation_no=quotation, + part_id=row.part_id, + process_station=self._clip(process_station_src, 20) if process_station_src else None, + unit=self._pick_prefill_value(prefill_fields, "2", "unit", row.unit), + unit_rate=self._pick_prefill_value(prefill_fields, "2", "unit_rate", row.unit_rate), + process_qty=self._pick_prefill_value(prefill_fields, "2", "process_qty", row.process_qty), + process_price=self._pick_prefill_value(prefill_fields, "2", "process_price", row.process_price), + remark=self._pick_prefill_value(prefill_fields, "2", "remark", row.remark), + option_json=row.option_json if prefill_fields.get("2") else None, + ) + ) + + for row in inquiry_others: + if row.part_id in part_ids: + other_bulk.append( + QuotationOther( + quotation_no=quotation, + part_id=row.part_id, + packaging_cost=self._pick_prefill_value(prefill_fields, "3", "packaging_cost", row.packaging_cost), + transportation_cost=self._pick_prefill_value(prefill_fields, "3", "transportation_cost", row.transportation_cost), + ) + ) + + for row in inquiry_profits: + if row.part_id in part_ids: + profit_bulk.append( + QuotationProfit( + quotation_no=quotation, + part_id=row.part_id, + tax_rate=self._pick_prefill_value(prefill_fields, "6", "tax_rate", row.tax_rate), + profit_rate=self._pick_prefill_value(prefill_fields, "5", "profit_rate", row.profit_rate), + ) + ) + + for row in inquiry_items: + if row.part_id in part_ids: + # 上阶物料核心字段与询价 `InquiryRfqItem` 一致,发布时始终带入报价单(不依赖模板 prefill 是否勾选「产品明细」列) + product_name_src = row.product_name + unit_src = row.unit + qty_src = row.qty + unit_price_src = row.unit_price + item_bulk.append( + QuotationItem( + quotation_no=quotation, + part_id=row.part_id, + product_name=self._clip(product_name_src or "", 20), + unit=self._clip(unit_src or "", 10), + qty=int(qty_src) if qty_src is not None else 0, + is_bom=row.is_bom or 0, + unit_price=unit_price_src, + product_cost=self._pick_prefill_value(prefill_fields, "7", "product_cost", None), + total_material_cost=self._pick_prefill_value(prefill_fields, "7", "total_material_cost", row.total_material_cost), + total_processing_cost=self._pick_prefill_value(prefill_fields, "7", "total_processing_cost", row.total_processing_cost), + total_other_expense=self._pick_prefill_value(prefill_fields, "7", "total_other_expense", row.total_other_expense), + total_opex_amt=self._pick_prefill_value(prefill_fields, "7", "total_opex_amt", row.total_opex_amt), + profit_rate=self._pick_prefill_value(prefill_fields, "7", "profit_rate", row.profit_rate), + total_price_excl_tax=self._pick_prefill_value(prefill_fields, "7", "total_price_excl_tax", row.total_price_excl_tax), + tax_rate=self._pick_prefill_value(prefill_fields, "7", "tax_rate", row.tax_rate), + total_price_incl_tax=self._pick_prefill_value(prefill_fields, "7", "total_price_incl_tax", row.total_price_incl_tax), + is_awarded=0, + winning_bid_price=None, + ) + ) + + if material_bulk: + QuotationMaterial.objects.bulk_create(material_bulk) + if process_bulk: + QuotationProcess.objects.bulk_create(process_bulk) + if other_bulk: + QuotationOther.objects.bulk_create(other_bulk) + if profit_bulk: + QuotationProfit.objects.bulk_create(profit_bulk) + if item_bulk: + QuotationItem.objects.bulk_create(item_bulk) + + def perform_create(self, serializer): + inquiry = serializer.save() + RFQOperationLogs.try_append( + inquiry_no=inquiry.inquiry_no, + purchase_type=int(inquiry.purchase_type), + operation_type=1, + operation_user=self._get_request_username() or None, + quotation_no="-", + per_status=None, + cur_status=int(inquiry.status) if inquiry.status is not None else 1, + operation_desc="询价单创建", + ) + + def perform_update(self, serializer): + serializer.save() + + def update(self, request, *args, **kwargs): + instance = self.get_object() + blocked = self._ensure_editable(instance) + if blocked: + return blocked + return super().update(request, *args, **kwargs) + + def partial_update(self, request, *args, **kwargs): + instance = self.get_object() + blocked = self._ensure_editable(instance) + if blocked: + return blocked + return super().partial_update(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + blocked = self._ensure_editable(instance) + if blocked: + return blocked + return super().destroy(request, *args, **kwargs) + + @action(methods=["delete"], detail=False) + def multiple_delete(self, request, *args, **kwargs): + keys = request.data.get("keys", None) + if not keys: + return ErrorResponse(msg="未获取到keys字段") + locked = self.get_queryset().filter(id__in=keys).exclude(status=self.STATUS_OPEN) + if locked.exists(): + return ErrorResponse(msg="仅“开立”状态的询价单允许删除") + self.get_queryset().filter(id__in=keys).delete() + return SuccessResponse(data=[], msg="删除成功") + + @action(methods=["put"], detail=True) + def confirm(self, request, pk=None): + instance = self.get_object() + if int(instance.status or self.STATUS_OPEN) != self.STATUS_OPEN: + return ErrorResponse(msg="只有“开立”状态的询价单才能确认") + try: + self._ensure_inquiry_has_supplier_groups( + instance, empty_message="请先维护询价单供应商名单后再确认" + ) + except serializers.ValidationError as exc: + detail = getattr(exc, "detail", None) + if isinstance(detail, (list, tuple)) and detail: + msg = str(detail[0]) + else: + msg = str(detail or exc) + return ErrorResponse(msg=msg) + current_user = self._get_request_username() or None + current_time = timezone.now() + return self._save_status( + instance, + status=self.STATUS_CONFIRMED, + confirm_user=current_user, + confirm_time=current_time, + release_user=getattr(instance, "release_user", None), + release_time=getattr(instance, "release_time", None), + operation_type=2, + operation_desc="询价单确认", + ) + + @action(methods=["put"], detail=True) + def restore(self, request, pk=None): + instance = self.get_object() + if int(instance.status or self.STATUS_OPEN) != self.STATUS_CONFIRMED: + return ErrorResponse(msg='只有“确认”状态的询价单才能还原为“开立”') + return self._save_status( + instance, + status=self.STATUS_OPEN, + confirm_user=None, + confirm_time=None, + release_user=None, + release_time=None, + operation_type=4, + operation_desc="询价单还原为开立", + ) + + @action(methods=["put"], detail=True) + def publish(self, request, pk=None): + instance = self.get_object() + if int(instance.status or self.STATUS_OPEN) != self.STATUS_CONFIRMED: + return ErrorResponse(msg='只有“确认”状态的询价单才能发布') + current_user = self._get_request_username() or None + current_time = timezone.now() + supplier_groups = self._group_inquiry_suppliers(instance) + supplier_count = len(supplier_groups) + try: + with transaction.atomic(): + self._create_supplier_quotations(instance) # 发布询价单时按供应商关联表,逐个创建对应的报价单 + self._notify_vendors_on_publish(instance) # 与子表汇总结果一致,逐供应商发送发布邮件 + return self._save_status( + instance, + status=self.STATUS_PUBLISHED, + confirm_user=getattr(instance, "confirm_user", None), + confirm_time=getattr(instance, "confirm_time", None), + release_user=current_user, + release_time=current_time, + operation_type=3, + operation_desc=f"询价单发布,共{supplier_count}个受邀供应商", + ) + except serializers.ValidationError as exc: + detail = getattr(exc, "detail", None) + if isinstance(detail, (list, tuple)) and detail: + msg = str(detail[0]) + else: + msg = str(detail or exc) + return ErrorResponse(msg=msg) + + @action(methods=["put"], detail=True) + def start_bargaining(self, request, pk=None): + """开启比价议价:从报价结束状态进入比议价中;并写入比价-制程最低价主/次表。""" + instance = self.get_object() + if int(instance.status or self.STATUS_PUBLISHED) not in (self.STATUS_PUBLISHED, self.STATUS_QUOTING, self.STATUS_QUOTE_ENDED): + return ErrorResponse(msg='只有“发布”或“报价结束”状态的询价单才能开启比价') + current_user = self._get_request_username() or None + current_time = timezone.now() + part_id = _resolve_inquiry_primary_part_id(instance) + try: + with transaction.atomic(): + if part_id: + _sync_misc_low_price_records(instance, part_id) + else: + logger.warning( + "start_bargaining: 询价单无上阶物料料号,跳过制程最低价落库 inquiry_no=%s", + instance.inquiry_no, + ) + return self._save_status( + instance, + status=self.STATUS_BARGaining, + confirm_user=getattr(instance, "confirm_user", None), + confirm_time=getattr(instance, "confirm_time", None), + release_user=getattr(instance, "release_user", None), + release_time=getattr(instance, "release_time", None), + comparison_user=current_user, + comparison_time=current_time, + operation_type=7, + operation_desc="开启比议价", + ) + except Exception as exc: + logger.exception("start_bargaining 写入制程最低价失败") + return ErrorResponse(msg=f"开启比价失败:{exc}") + + @action(methods=["put"], detail=True) + def confirm_negotiation(self, request, pk=None): + """确认议价:从比议价中进入议价确认/价格审核""" + instance = self.get_object() + if int(instance.status or self.STATUS_BARGaining) != self.STATUS_BARGaining: + return ErrorResponse(msg='只有“比议价中”状态的询价单才能确认议价') + part_id = _resolve_inquiry_primary_part_id(instance) + if not part_id: + return ErrorResponse(msg="询价单无上阶物料料号,无法确认比价") + try: + with transaction.atomic(): + _sync_misc_low_price_records(instance, part_id) + return self._save_status( + instance, + status=self.STATUS_NEGOTIATED, + confirm_user=getattr(instance, "confirm_user", None), + confirm_time=getattr(instance, "confirm_time", None), + release_user=getattr(instance, "release_user", None), + release_time=getattr(instance, "release_time", None), + comparison_user=getattr(instance, "comparison_user", None), + comparison_time=getattr(instance, "comparison_time", None), + operation_type=8, + operation_desc="议价审核提交(进入价格审核)", + ) + except Exception as exc: + return ErrorResponse(msg=f"确认比价失败:{exc}") + + @action(methods=["put"], detail=True) + def submit_price_audit(self, request, pk=None): + """价格审核提交:由「价格审核」(7) 进入「核价通过」(8)。""" + instance = self.get_object() + if int(instance.status or 0) != self.STATUS_NEGOTIATED: + return ErrorResponse(msg='只有「价格审核」状态的询价单才能提交核价') + return self._save_status( + instance, + status=self.STATUS_APPROVED, + confirm_user=getattr(instance, "confirm_user", None), + confirm_time=getattr(instance, "confirm_time", None), + release_user=getattr(instance, "release_user", None), + release_time=getattr(instance, "release_time", None), + comparison_user=getattr(instance, "comparison_user", None), + comparison_time=getattr(instance, "comparison_time", None), + operation_type=9, + operation_desc="议价审核完成(核价通过)", + ) + + @action(methods=["get"], detail=True, url_path="negotiation_records") + def negotiation_records(self, request, pk=None): + """按询价单号查询杂采议价记录(可选 part_id)。""" + instance = self.get_object() + part_id = (request.query_params.get("part_id") or "").strip() + if not part_id: + part_id = _resolve_inquiry_primary_part_id(instance) + qs = MiscNegotiationRecords.objects.filter(inquiry_no=instance.inquiry_no) + if part_id: + qs = qs.filter(part_id=part_id) + data = MiscNegotiationRecordsSerializer(qs.order_by("id"), many=True).data + return DetailResponse(data=data, msg="success") + + @action(methods=["get"], detail=True, url_path="operation_logs") + def operation_logs(self, request, pk=None): + """按询价单号查询该询价单全部操作日志(``pis_rfq_operation_logs``)。""" + instance = self.get_object() + qs = ( + RFQOperationLogs.objects.filter(inquiry_no=instance.inquiry_no) + .order_by("-operation_time", "-create_datetime", "-id") + ) + data = RFQOperationLogsSerializer(qs, many=True).data + return DetailResponse(data=data, msg="success") + + @action(methods=["get"], detail=True, url_path="invited_supplier_quotations") + def invited_supplier_quotations(self, request, pk=None): + """受邀供应商报价单一览(与发布生成的 ``QuotationMaster`` 一致,供操作日志嵌套表使用)。""" + instance = self.get_object() + data = build_invited_supplier_quotation_rows(instance) + return DetailResponse(data=data, msg="success") + + @action(methods=["put"], detail=True, url_path="save_negotiation_records") + def save_negotiation_records(self, request, pk=None): + """按报价单写入杂采议价记录:议价结果 + 该报价单议价前含税/不含税总价快照(来自上阶物料明细)。""" + instance = self.get_object() + serializer = MiscNegotiationSaveSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + part_id = (serializer.validated_data.get("part_id") or "").strip() + if not part_id: + part_id = _resolve_inquiry_primary_part_id(instance) + if not part_id: + return ErrorResponse(msg="询价单无上阶物料料号,无法保存议价记录") + records = serializer.validated_data.get("records") or [] + username = self._get_request_username() or None + now = timezone.now() + with transaction.atomic(): + for row in records: + qn = (row.get("quotation_no") or "").strip() + if not qn: + return ErrorResponse(msg="records 中每条须包含 quotation_no(报价单号)") + code = (row.get("supplier_code") or "").strip() + if not code: + qm = QuotationMaster.objects.filter(quotation_no=qn).only("supplier_code").first() + if qm: + code = (qm.supplier_code or "").strip() + is_awarded = int(row.get("is_awarded") or 0) + # 议价价格独立于是否中标:用户填写即落库(清空时前端传 null) + bp = row.get("bargaining_price") + snap_ex, snap_in = _negotiation_totals_from_quotation_item(qn, part_id) + tex = row.get("total_price_excl_tax") + tin = row.get("total_price_incl_tax") + if tex is None: + tex = snap_ex + if tin is None: + tin = snap_in + if tex is None: + tex = Decimal("0") + if tin is None: + tin = Decimal("0") + defaults = { + "supplier_code": code or None, + "is_awarded": is_awarded, + "bargaining_user": username, + "bargaining_time": now, + "bargaining_price": bp, + "quotation_no": qn, + "total_price_excl_tax": tex, + "total_price_incl_tax": tin, + } + qs = MiscNegotiationRecords.objects.filter( + inquiry_no=instance.inquiry_no, + part_id=part_id, + quotation_no=qn, + ) + if qs.count() > 1: + keep_id = qs.order_by("id").first().id + qs.exclude(id=keep_id).delete() + obj = ( + MiscNegotiationRecords.objects.filter( + inquiry_no=instance.inquiry_no, + part_id=part_id, + quotation_no=qn, + ) + .order_by("id") + .first() + ) + if obj: + for k, v in defaults.items(): + setattr(obj, k, v) + obj.save() + else: + MiscNegotiationRecords.objects.create( + inquiry_no=instance.inquiry_no, + part_id=part_id, + **defaults, + ) + out_qs = MiscNegotiationRecords.objects.filter( + inquiry_no=instance.inquiry_no, + part_id=part_id, + ).order_by("id") + return DetailResponse( + data=MiscNegotiationRecordsSerializer(out_qs, many=True).data, + msg="议价记录已保存", + ) + + def _notify_vendors_on_publish(self, inquiry: Inquiry): + """ + 按询价单供应商子表汇总(与 `_create_supplier_quotations` 相同的 `_group_inquiry_suppliers`), + 每个供应商分组一封邮件,避免同一供应商多行子表重复发送。 + """ + supplier_groups = self._group_inquiry_suppliers(inquiry) + if not supplier_groups: + return + + purchaser_company_name = self._resolve_purchaser_company_name(inquiry) + is_bidding = int(getattr(inquiry, "buying_method", 1) or 1) == 2 + + for vendor in supplier_groups: + if not isinstance(vendor, dict): + continue + + if is_bidding: + send_bids_publish_notice_to_supplier( + inquiry, + vendor, + purchaser_company_name=purchaser_company_name, + ) + continue + + supplier_name = (vendor.get("supplier_name") or "").strip() + email = (vendor.get("contact_email") or "").strip() + to_list = [email] if email else [] + + ctx = build_context_rfs_publish( + inquiry, + vendor, + purchaser_company_name=purchaser_company_name, + ) + subject, body = render_email(TEMPLATE_RFS_PUBLISH, ctx) + + notice = EmailNotice.objects.create( + subject=subject, + body=body, + to_emails=to_list, + cc_emails=[], + bcc_emails=[], + attachments=[], + biz_type="inquiry", + biz_id=inquiry.inquiry_no, + status="pending", + payload={ + "template_key": TEMPLATE_RFS_PUBLISH, + "is_html": True, + "inquiry_no": inquiry.inquiry_no, + "inquiry_title": inquiry.title, + "quote_deadline": ctx.get("deadline_time"), + "supplier_name": supplier_name, + }, + ) + + if not to_list: + notice.status = "failed" + notice.last_error = "缺少供应商邮箱" + notice.save(update_fields=["status", "last_error", "update_datetime"]) + continue + + notice.status = "sending" + notice.save(update_fields=["status", "update_datetime"]) + + success, detail = send_email_notice(notice) + notice.response = detail or {} + if success: + notice.status = "success" + notice.sent_at = timezone.now() + notice.last_error = None + else: + notice.status = "failed" + notice.last_error = detail.get("error") if isinstance(detail, dict) else str(detail) + + notice.save(update_fields=["status", "sent_at", "response", "last_error", "update_datetime"]) + + +class InquirySupplierViewSet(CustomModelViewSet): + queryset = InquirySupplier.objects.all() + serializer_class = InquirySupplierSerializer + filter_fields = ("inquiry_no", "supplier_code", "supplier_name") + search_fields = ("inquiry_no", "supplier_code", "supplier_name", "contact_person") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryAttachmentViewSet(CustomModelViewSet): + queryset = InquiryAttachment.objects.all() + serializer_class = InquiryAttachmentSerializer + filter_fields = ("inquiry_no", "file_type", "file_name") + search_fields = ("inquiry_no", "file_name", "file_type") + ordering = ("-upload_time",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryMaterialCostViewSet(CustomModelViewSet): + queryset = InquiryMaterialCost.objects.all() + serializer_class = InquiryMaterialCostSerializer + filter_fields = ("inquiry_no", "part_id", "material_spec") + search_fields = ("inquiry_no", "part_id", "material_spec", "remark") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryProcessCostViewSet(CustomModelViewSet): + queryset = InquiryProcessCost.objects.all() + serializer_class = InquiryProcessCostSerializer + filter_fields = ("inquiry_no", "part_id", "process_station") + search_fields = ("inquiry_no", "part_id", "process_station", "remark") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryOtherCostViewSet(CustomModelViewSet): + queryset = InquiryOtherCost.objects.all() + serializer_class = InquiryOtherCostSerializer + filter_fields = ("inquiry_no", "part_id") + search_fields = ("inquiry_no", "part_id") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryProfitCostViewSet(CustomModelViewSet): + queryset = InquiryProfitCost.objects.all() + serializer_class = InquiryProfitCostSerializer + filter_fields = ("inquiry_no", "part_id") + search_fields = ("inquiry_no", "part_id") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class InquiryRfqItemViewSet(CustomModelViewSet): + queryset = InquiryRfqItem.objects.all() + serializer_class = InquiryRfqItemSerializer + filter_fields = ("inquiry_no", "part_id", "product_name") + search_fields = ("inquiry_no", "part_id", "product_name") + ordering = ("id",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class MiscLowPriceHeaderViewSet(CustomModelViewSet): + """比价-制程最低价记录主表""" + + queryset = MiscLowPriceHeader.objects.all() + serializer_class = MiscLowPriceHeaderSerializer + filter_fields = ("inquiry_no", "part_id", "souce_no", "cost_type", "item_no") + search_fields = ("inquiry_no", "part_id", "souce_no", "item_no") + ordering = ("-create_datetime", "id") + + +class MiscLowPriceDetailViewSet(CustomModelViewSet): + """比价-制程最低价记录次表""" + + queryset = MiscLowPriceDetail.objects.all() + serializer_class = MiscLowPriceDetailSerializer + filter_fields = ("inquiry_no", "part_id", "cost_type", "material_spec", "item_no", "souce_no") + search_fields = ("inquiry_no", "part_id", "material_spec", "item_no", "souce_no") + ordering = ("-create_datetime", "id") + + +class MiscNegotiationRecordsViewSet(CustomModelViewSet): + """杂采议价记录表""" + + queryset = MiscNegotiationRecords.objects.all() + serializer_class = MiscNegotiationRecordsSerializer + filter_fields = ("inquiry_no", "part_id", "supplier_code") + search_fields = ("inquiry_no", "part_id", "supplier_code") + ordering = ("-create_datetime", "id") + + +class RFQOperationLogsViewSet(CustomModelViewSet): + """询价单操作日志(rfq_operation_logs)""" + + queryset = RFQOperationLogs.objects.all() + serializer_class = RFQOperationLogsSerializer + create_serializer_class = RFQOperationLogsSerializer + update_serializer_class = RFQOperationLogsSerializer + filter_fields = ( + "quotation_no", + "operation_type", + "operation_user", + "purchase_type", + ) + search_fields = ("inquiry_no", "quotation_no", "operation_user", "operation_desc") + ordering = ("-create_datetime", "-id") + + def filter_queryset(self, queryset): + """询价单号模糊查询、按询价主表采购负责人筛选(GET 参数 ``inquiry_no``、``buyer``)。""" + queryset = super().filter_queryset(queryset) + inquiry_no = (self.request.query_params.get("inquiry_no") or "").strip() + if inquiry_no: + queryset = queryset.filter(inquiry_no__icontains=inquiry_no) + buyer = (self.request.query_params.get("buyer") or "").strip() + if buyer: + nos = Inquiry.objects.filter(buyer__icontains=buyer).values_list("inquiry_no", flat=True) + queryset = queryset.filter(inquiry_no__in=list(nos)) + return queryset + + +class MiscProcMaterialMinPricesViewSet(CustomModelViewSet): + """杂采材料制程最低价信息表视图集 + + 按采购件料号、材料规格、货币代码、项次存储最低重量(1)和最低单价(2)信息。 + """ + + queryset = MiscProcMaterialMinPrices.objects.all() + serializer_class = MiscProcMaterialMinPricesSerializer + filter_fields = ( + "part_id", + "material_spec", + "currency_code", + "item_num", + ) + search_fields = ( + "part_id", + "material_spec", + "source_number", + ) + ordering = ("-create_datetime", "id") + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class MiscProcProcessingMinPricesViewSet(CustomModelViewSet): + """杂采加工费用最低价信息表视图集 + + 按采购件料号、货币代码存储各报价单加工费合计的最低值。 + """ + + queryset = MiscProcProcessingMinPrices.objects.all() + serializer_class = MiscProcProcessingMinPricesSerializer + filter_fields = ( + "part_id", + "currency_code", + ) + search_fields = ( + "part_id", + "source_number", + ) + ordering = ("-create_datetime", "id") + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() \ No newline at end of file diff --git a/backend/apps/pissupplier/__init__.py b/backend/apps/pissupplier/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/apps/pissupplier/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/apps/pissupplier/apps.py b/backend/apps/pissupplier/apps.py new file mode 100644 index 0000000..0288024 --- /dev/null +++ b/backend/apps/pissupplier/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class SupplierConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.pissupplier" + verbose_name = "报价管理" diff --git a/backend/apps/pissupplier/migrations/0001_initial.py b/backend/apps/pissupplier/migrations/0001_initial.py new file mode 100644 index 0000000..074f23f --- /dev/null +++ b/backend/apps/pissupplier/migrations/0001_initial.py @@ -0,0 +1,177 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='QuotationMaster', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('quotation_no', models.CharField(db_index=True, max_length=20, unique=True, verbose_name='报价单单号')), + ('inquiry_no', models.CharField(db_index=True, help_text='报价单关联的询价单单号', max_length=20, verbose_name='询价单单号')), + ('supplier_code', models.CharField(max_length=20, verbose_name='供应商代码')), + ('supplier_name', models.CharField(max_length=20, verbose_name='供应商名称')), + ('contact_person', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系人')), + ('contact_phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系人电话')), + ('contact_email', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系人邮件')), + ('quote_deadline', models.CharField(blank=True, max_length=20, null=True, verbose_name='报价截止时间')), + ('delivery_days', models.IntegerField(blank=True, null=True, verbose_name='交货周期')), + ('payment_method', models.IntegerField(blank=True, choices=[(1, '月结30天'), (2, '月结60天'), (3, '货到付款'), (4, '预付30%,余款次月结')], null=True, verbose_name='付款方式')), + ('status', models.IntegerField(blank=True, choices=[(1, '待报价'), (2, '已报价'), (3, '已过期')], default=1, help_text='1 待报价 2 已报价 3 已过期;置为已报价请走供应商端 POST quotation_master/{id}/submit/', null=True, verbose_name='状态')), + ('is_awarded', models.IntegerField(blank=True, choices=[(0, '未中标'), (1, '已中标')], default=0, null=True, verbose_name='报价中标否')), + ('createuser', models.CharField(blank=True, max_length=20, null=True, verbose_name='创建人员')), + ('creattime', models.DateTimeField(blank=True, null=True, verbose_name='创建时间')), + ('quoteuser', models.CharField(blank=True, max_length=20, null=True, verbose_name='报价人员')), + ('quotetime', models.DateTimeField(blank=True, help_text='正式提交报价时由 submit 接口写入当前时间', null=True, verbose_name='报价时间')), + ('remark', models.TextField(blank=True, null=True, verbose_name='报价说明及备注')), + ], + options={ + 'verbose_name': '杂采报价单主表', + 'verbose_name_plural': '杂采报价单主表', + 'db_table': 'pis_sup_quotation_master', + 'ordering': ('-creattime', '-autoid'), + }, + ), + migrations.CreateModel( + name='QuotationProfit', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('tax_rate', models.DecimalField(blank=True, db_column='tax_rate', decimal_places=4, max_digits=12, null=True, verbose_name='税率')), + ('profit_rate', models.DecimalField(blank=True, db_column='profit_rate', decimal_places=4, max_digits=12, null=True, verbose_name='利润率')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='profit_costs', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-税率利润明细表', + 'verbose_name_plural': '杂采报价单-税率利润明细表', + 'db_table': 'pis_sup_quotation_profit', + 'ordering': ('autoid',), + 'indexes': [models.Index(fields=['part_id'], name='pis_sup_quo_Partid_b34018_idx')], + }, + ), + migrations.CreateModel( + name='QuotationProcess', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('process_station', models.CharField(blank=True, db_column='process_station', max_length=20, null=True, verbose_name='加工工站')), + ('unit', models.DecimalField(blank=True, db_column='Unit', decimal_places=4, max_digits=12, null=True, verbose_name='单位')), + ('unit_rate', models.DecimalField(blank=True, db_column='UnitRate', decimal_places=4, max_digits=12, null=True, verbose_name='费率')), + ('process_qty', models.CharField(blank=True, db_column='ProcessQty', max_length=20, null=True, verbose_name='加工计量')), + ('process_price', models.DecimalField(blank=True, db_column='ProcessPrice', decimal_places=2, max_digits=10, null=True, verbose_name='加工费用')), + ('remark', models.CharField(blank=True, db_column='remark', max_length=100, null=True, verbose_name='备注')), + ('option_json', models.TextField(blank=True, db_column='OptionJson', null=True, verbose_name='可选扩展信息')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='process_costs', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-加工成本明细表', + 'verbose_name_plural': '杂采报价单-加工成本明细表', + 'db_table': 'pis_sup_quotation_process', + 'ordering': ('autoid',), + 'indexes': [models.Index(fields=['part_id'], name='pis_sup_quo_Partid_e14a9f_idx')], + 'unique_together': {('quotation_no', 'part_id', 'process_station')}, + }, + ), + migrations.CreateModel( + name='QuotationOther', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('packaging_cost', models.DecimalField(blank=True, db_column='packaging_cost', decimal_places=4, max_digits=12, null=True, verbose_name='包装费用')), + ('transportation_cost', models.DecimalField(blank=True, db_column='transportation_cost', decimal_places=4, max_digits=12, null=True, verbose_name='运输费用')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='other_costs', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-其他费用明细表', + 'verbose_name_plural': '杂采报价单-其他费用明细表', + 'db_table': 'pis_sup_quotation_other', + 'ordering': ('autoid',), + 'indexes': [models.Index(fields=['part_id'], name='pis_sup_quo_Partid_f50af0_idx')], + }, + ), + migrations.CreateModel( + name='QuotationMaterial', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('material_spec', models.CharField(db_column='material_spec', max_length=20, verbose_name='材料规格')), + ('length', models.CharField(blank=True, db_column='Length', max_length=10, null=True, verbose_name='长')), + ('width', models.CharField(blank=True, db_column='Width', max_length=10, null=True, verbose_name='宽')), + ('height', models.CharField(blank=True, db_column='Height', max_length=10, null=True, verbose_name='高')), + ('unit_price', models.DecimalField(blank=True, db_column='UnitPrice', decimal_places=2, max_digits=10, null=True, verbose_name='单价')), + ('qty', models.IntegerField(blank=True, db_column='qty', null=True, verbose_name='数量')), + ('specific_gravity', models.CharField(blank=True, db_column='SpecificGravity', max_length=10, null=True, verbose_name='比重')), + ('material_cost', models.DecimalField(blank=True, db_column='material_cost', decimal_places=4, max_digits=12, null=True, verbose_name='材料费用')), + ('remark', models.CharField(blank=True, db_column='remark', max_length=100, null=True, verbose_name='备注')), + ('option_json', models.TextField(blank=True, db_column='OptionJson', null=True, verbose_name='可选扩展信息')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='material_costs', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-材料成本明细表', + 'verbose_name_plural': '杂采报价单-材料成本明细表', + 'db_table': 'pis_sup_quotation_material', + 'ordering': ('autoid',), + 'indexes': [models.Index(fields=['part_id'], name='pis_sup_quo_Partid_cf933e_idx')], + 'unique_together': {('quotation_no', 'part_id', 'material_spec')}, + }, + ), + migrations.CreateModel( + name='QuotationItem', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('product_name', models.CharField(db_column='product_name', max_length=20, verbose_name='产品名称')), + ('unit', models.CharField(db_column='unit', max_length=10, verbose_name='计量单位')), + ('qty', models.IntegerField(db_column='qty', verbose_name='采购数量')), + ('is_bom', models.IntegerField(db_column='IsBom', verbose_name='是否成本结构')), + ('unit_price', models.DecimalField(blank=True, db_column='unit_price', decimal_places=4, max_digits=12, null=True, verbose_name='标准品单价')), + ('product_cost', models.DecimalField(blank=True, db_column='product_cost', decimal_places=4, max_digits=12, null=True, verbose_name='标准品总价')), + ('total_material_cost', models.DecimalField(blank=True, db_column='total_material_cost', decimal_places=4, max_digits=12, null=True, verbose_name='材料成本合计')), + ('total_processing_cost', models.DecimalField(blank=True, db_column='total_processing_cost', decimal_places=4, max_digits=12, null=True, verbose_name='加工成本合计')), + ('total_other_expense', models.DecimalField(blank=True, db_column='total_other_expense', decimal_places=4, max_digits=12, null=True, verbose_name='其他费用合计')), + ('total_opex_amt', models.DecimalField(blank=True, db_column='total_opex_amt', decimal_places=4, max_digits=12, null=True, verbose_name='管销研费用')), + ('profit_rate', models.DecimalField(blank=True, db_column='profit_rate', decimal_places=4, max_digits=12, null=True, verbose_name='利润')), + ('total_price_excl_tax', models.DecimalField(blank=True, db_column='total_price_excl_tax', decimal_places=4, max_digits=12, null=True, verbose_name='不含税总价')), + ('tax_rate', models.DecimalField(blank=True, db_column='tax_rate', decimal_places=4, max_digits=12, null=True, verbose_name='税费')), + ('total_price_incl_tax', models.DecimalField(blank=True, db_column='total_price_incl_tax', decimal_places=4, max_digits=12, null=True, verbose_name='含税总价')), + ('is_awarded', models.IntegerField(blank=True, choices=[(0, '未中标'), (1, '中标')], db_column='is_awarded', null=True, verbose_name='报价中标否')), + ('winning_bid_price', models.DecimalField(blank=True, db_column='winning_bid_price', decimal_places=4, max_digits=12, null=True, verbose_name='中标价格')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='rfq_items', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-上阶物料明细表', + 'verbose_name_plural': '杂采报价单-上阶物料明细表', + 'db_table': 'pis_sup_quot_items', + 'ordering': ('autoid',), + 'indexes': [models.Index(fields=['part_id'], name='pis_sup_quo_Partid_99a155_idx')], + }, + ), + migrations.CreateModel( + name='QuotationAttachment', + fields=[ + ('autoid', models.BigAutoField(db_column='autoId', primary_key=True, serialize=False, verbose_name='自增ID')), + ('part_id', models.CharField(db_column='Partid', max_length=50, verbose_name='产品料号')), + ('file_name', models.CharField(db_column='file_name', max_length=20, verbose_name='文件名称')), + ('file_path', models.CharField(blank=True, db_column='file_path', max_length=20, null=True, verbose_name='文件路径')), + ('uploadtime', models.CharField(blank=True, max_length=20, null=True, verbose_name='上传时间')), + ('uploaduser', models.CharField(blank=True, max_length=20, null=True, verbose_name='上传人员')), + ('quotation_no', models.ForeignKey(db_column='quotation_no', on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='pissupplier.quotationmaster', to_field='quotation_no', verbose_name='报价单单号')), + ], + options={ + 'verbose_name': '杂采报价单-附件关联表', + 'verbose_name_plural': '杂采报价单-附件关联表', + 'db_table': 'pis_sup_quotation_attachment', + 'ordering': ('-autoid',), + 'unique_together': {('quotation_no', 'part_id', 'file_name')}, + }, + ), + ] diff --git a/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py b/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py new file mode 100644 index 0000000..e06e690 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0002_quotationmaster_validity_days_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.14 on 2026-03-24 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaster', + name='validity_days', + field=models.IntegerField(blank=True, null=True, verbose_name='有效天数'), + ), + migrations.AlterField( + model_name='quotationprocess', + name='unit', + field=models.CharField(blank=True, db_column='Unit', max_length=20, null=True, verbose_name='单位'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py b/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py new file mode 100644 index 0000000..35ce847 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0003_quotationmaterial_weight_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.14 on 2026-03-26 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0002_quotationmaster_validity_days_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaterial', + name='weight', + field=models.DecimalField(blank=True, db_column='Weight', decimal_places=4, max_digits=12, null=True, verbose_name='重量'), + ), + migrations.AlterField( + model_name='quotationattachment', + name='file_name', + field=models.CharField(db_column='file_name', max_length=100, verbose_name='文件名称'), + ), + migrations.AlterField( + model_name='quotationattachment', + name='file_path', + field=models.CharField(blank=True, db_column='file_path', max_length=200, null=True, verbose_name='文件路径'), + ), + migrations.AlterField( + model_name='quotationmaster', + name='status', + field=models.IntegerField(blank=True, choices=[(1, '待报价'), (2, '报价中'), (3, '已报价'), (4, '已过期')], default=1, help_text='1 待报价 2 已报价 3 已过期;置为已报价请走供应商端 POST quotation_master/{id}/submit/', null=True, verbose_name='状态'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py b/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py new file mode 100644 index 0000000..0795e18 --- /dev/null +++ b/backend/apps/pissupplier/migrations/0004_quotationmaster_bid_end_time_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.14 on 2026-03-27 16:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0003_quotationmaterial_weight_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='quotationmaster', + name='bid_end_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标截止时间'), + ), + migrations.AddField( + model_name='quotationmaster', + name='bid_start_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='投标开始时间'), + ), + migrations.AddField( + model_name='quotationmaster', + name='buying_method', + field=models.IntegerField(blank=True, choices=[(1, '询价'), (2, '招标')], null=True, verbose_name='采购方式(寻源方式)'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py b/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py new file mode 100644 index 0000000..2a9354c --- /dev/null +++ b/backend/apps/pissupplier/migrations/0005_alter_quotationmaster_quote_deadline_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.14 on 2026-03-29 19:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pissupplier', '0004_quotationmaster_bid_end_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='quotationmaster', + name='quote_deadline', + field=models.DateTimeField(blank=True, null=True, verbose_name='报价截止时间'), + ), + migrations.AlterField( + model_name='quotationmaster', + name='status', + field=models.IntegerField(blank=True, choices=[(1, '待报价'), (2, '报价中'), (3, '已报价'), (4, '已过期')], default=1, help_text='1 待报价 2 报价中 3 已报价 4 已过期;置为已报价请走 POST quotation_master/{id}/submit/;超时批量置过期及询价单收口见 sync_expired / Inquiry.sync_to_quote_closed_when_no_open_quotations;名单内供应商均已提交见 submit 与 Inquiry.sync_to_quote_closed_when_all_suppliers_quoted。', null=True, verbose_name='状态'), + ), + ] diff --git a/backend/apps/pissupplier/migrations/__init__.py b/backend/apps/pissupplier/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/apps/pissupplier/models.py b/backend/apps/pissupplier/models.py new file mode 100644 index 0000000..8b3ab02 --- /dev/null +++ b/backend/apps/pissupplier/models.py @@ -0,0 +1,453 @@ +from django.db import models + +from dvadmin.utils.models import table_prefix + + +class QuotationMaster(models.Model): + """杂采报价单主表(采购端比价弹窗按 autoid 拉详情用于供应商报价预览)""" + + PAYMENT_METHOD_CHOICES = ( + (1, "月结30天"), + (2, "月结60天"), + (3, "货到付款"), + (4, "预付30%,余款次月结"), + ) + + STATUS_CHOICES = ( + (1, "待报价"), + (2, "报价中"), + (3, "已报价"), + (4, "已过期"), + ) + # 供应商仪表盘「待报价清单」仅包含:待报价(业务上常称「未报价」)、报价中 + DASHBOARD_PENDING_STATUSES = (1, 2) + + AWARD_STATUS_CHOICES = ( + (0, "未中标"), + (1, "已中标"), + ) + + BUYING_METHOD_CHOICES = ( + (1, "询价"), + (2, "招标"), + ) + + autoid = models.BigAutoField( primary_key=True, db_column="autoId", verbose_name="自增ID" ) + quotation_no = models.CharField( max_length=20, unique=True, db_index=True, verbose_name="报价单单号" ) + inquiry_no = models.CharField( max_length=20, db_index=True, verbose_name="询价单单号", help_text="报价单关联的询价单单号" ) + supplier_code = models.CharField( max_length=20, verbose_name="供应商代码" ) + supplier_name = models.CharField( max_length=20, verbose_name="供应商名称" ) + contact_person = models.CharField( max_length=20, null=True, blank=True, verbose_name="联系人" ) + contact_phone = models.CharField( max_length=20, null=True, blank=True, verbose_name="联系人电话" ) + contact_email = models.CharField( max_length=20, null=True, blank=True, verbose_name="联系人邮件" ) + quote_deadline = models.DateTimeField( null=True, blank=True, verbose_name="报价截止时间" ) + validity_days = models.IntegerField( null=True, blank=True, verbose_name="有效天数" ) + delivery_days = models.IntegerField( null=True, blank=True, verbose_name="交货周期" ) + payment_method = models.IntegerField( choices=PAYMENT_METHOD_CHOICES, null=True, blank=True, verbose_name="付款方式" ) + status = models.IntegerField( + choices=STATUS_CHOICES, + default=1, + null=True, + blank=True, + verbose_name="状态", + help_text=( + "1 待报价 2 报价中 3 已报价 4 已过期;" + "置为已报价请走 POST quotation_master/{id}/submit/;" + "超时批量置过期及询价单收口见 sync_expired / Inquiry.sync_to_quote_closed_when_no_open_quotations;" + "名单内供应商均已提交见 submit 与 Inquiry.sync_to_quote_closed_when_all_suppliers_quoted。" + ), + ) + is_awarded = models.IntegerField( choices=AWARD_STATUS_CHOICES, default=0, null=True, blank=True, verbose_name="报价中标否" ) + createuser = models.CharField( max_length=20, null=True, blank=True, verbose_name="创建人员" ) + creattime = models.DateTimeField( null=True, blank=True, verbose_name="创建时间" ) + quoteuser = models.CharField( max_length=20, null=True, blank=True, verbose_name="报价人员" ) + quotetime = models.DateTimeField( + null=True, blank=True, verbose_name="报价时间", help_text="正式提交报价时由 submit 接口写入当前时间" + ) + remark = models.TextField( null=True, blank=True, verbose_name="报价说明及备注" ) + buying_method = models.IntegerField(choices=BUYING_METHOD_CHOICES, null=True, blank=True, verbose_name="采购方式(寻源方式)") + bid_start_time = models.DateTimeField(null=True, blank=True, verbose_name="投标开始时间") + bid_end_time = models.DateTimeField(null=True, blank=True, verbose_name="投标截止时间") + + class Meta: + db_table = table_prefix + "misc_sup_quotation_master" + verbose_name = "杂采报价单主表" + verbose_name_plural = verbose_name + ordering = ("-creattime", "-autoid") + + def __str__(self): + return self.quotation_no + + +class QuotationAttachment(models.Model): + """杂采报价单-附件关联表""" + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="attachments", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + file_name = models.CharField(max_length=100, db_column="file_name", verbose_name="文件名称") + file_path = models.CharField(max_length=200, db_column="file_path", null=True, blank=True, verbose_name="文件路径") + uploadtime = models.CharField(max_length=20, null=True, blank=True, verbose_name="上传时间") + uploaduser = models.CharField(max_length=20, null=True, blank=True, verbose_name="上传人员") + + class Meta: + db_table = table_prefix + "misc_sup_quotation_attachment" + verbose_name = "杂采报价单-附件关联表" + verbose_name_plural = verbose_name + ordering = ("-autoid",) + unique_together = ("quotation_no", "part_id", "file_name") + + def __str__(self): + return self.file_name + + +class QuotationMaterial(models.Model): + """杂采报价单-材料成本明细表""" + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="material_costs", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + material_spec = models.CharField(max_length=20, db_column="material_spec", verbose_name="材料规格") + length = models.CharField(max_length=10, db_column="Length", null=True, blank=True, verbose_name="长") + width = models.CharField(max_length=10, db_column="Width", null=True, blank=True, verbose_name="宽") + height = models.CharField(max_length=10, db_column="Height", null=True, blank=True, verbose_name="高") + unit_price = models.DecimalField( + max_digits=10, + decimal_places=2, + db_column="UnitPrice", + null=True, + blank=True, + verbose_name="单价", + ) + qty = models.IntegerField(db_column="qty", null=True, blank=True, verbose_name="数量") + specific_gravity = models.CharField( + max_length=10, + db_column="SpecificGravity", + null=True, + blank=True, + verbose_name="比重", + ) + material_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="material_cost", + null=True, + blank=True, + verbose_name="材料费用", + ) + weight = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="Weight", + null=True, + blank=True, + verbose_name="重量", + ) + remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注") + option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息") + + class Meta: + db_table = table_prefix + "misc_sup_quotation_material" + verbose_name = "杂采报价单-材料成本明细表" + verbose_name_plural = verbose_name + ordering = ("autoid",) + unique_together = ("quotation_no", "part_id", "material_spec") + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self): + return f"{self.quotation_no_id}-{self.part_id}-{self.material_spec}" + + +class QuotationProcess(models.Model): + """杂采报价单-加工成本明细表""" + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="process_costs", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + process_station = models.CharField( + max_length=20, + db_column="process_station", + null=True, + blank=True, + verbose_name="加工工站", + ) + # 单位为文本(如 PCS/小时/cm),与 miscprocurement.InquiryProcessCost 一致 + unit = models.CharField( + max_length=20, + db_column="Unit", + null=True, + blank=True, + verbose_name="单位", + ) + unit_rate = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="UnitRate", + null=True, + blank=True, + verbose_name="费率", + ) + process_qty = models.CharField(max_length=20, db_column="ProcessQty", null=True, blank=True, verbose_name="加工计量") + process_price = models.DecimalField( + max_digits=10, + decimal_places=2, + db_column="ProcessPrice", + null=True, + blank=True, + verbose_name="加工费用", + ) + remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注") + option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息") + + class Meta: + db_table = table_prefix + "misc_sup_quotation_process" + verbose_name = "杂采报价单-加工成本明细表" + verbose_name_plural = verbose_name + ordering = ("autoid",) + unique_together = ("quotation_no", "part_id", "process_station") + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self): + return f"{self.quotation_no_id}-{self.part_id}-{self.process_station}" + + +class QuotationOther(models.Model): + """杂采报价单-其他费用明细表""" + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="other_costs", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + packaging_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="packaging_cost", + null=True, + blank=True, + verbose_name="包装费用", + ) + transportation_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="transportation_cost", + null=True, + blank=True, + verbose_name="运输费用", + ) + + class Meta: + db_table = table_prefix + "misc_sup_quotation_other" + verbose_name = "杂采报价单-其他费用明细表" + verbose_name_plural = verbose_name + ordering = ("autoid",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self): + return f"{self.quotation_no_id}-{self.part_id}" + + +class QuotationProfit(models.Model): + """杂采报价单-税率利润明细表""" + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="profit_costs", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + tax_rate = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="tax_rate", + null=True, + blank=True, + verbose_name="税率", + ) + profit_rate = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="profit_rate", + null=True, + blank=True, + verbose_name="利润率", + ) + + class Meta: + db_table = table_prefix + "misc_sup_quotation_profit" + verbose_name = "杂采报价单-税率利润明细表" + verbose_name_plural = verbose_name + ordering = ("autoid",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self): + return f"{self.quotation_no_id}-{self.part_id}" + + +class QuotationItem(models.Model): + """杂采报价单-上阶物料明细表""" + + ITEM_AWARD_STATUS_CHOICES = ( + (0, "未中标"), + (1, "中标"), + ) + + autoid = models.BigAutoField(primary_key=True, db_column="autoId", verbose_name="自增ID") + quotation_no = models.ForeignKey( + QuotationMaster, + to_field="quotation_no", + db_column="quotation_no", + on_delete=models.CASCADE, + related_name="rfq_items", + verbose_name="报价单单号", + ) + part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号") + product_name = models.CharField(max_length=20, db_column="product_name", verbose_name="产品名称") + unit = models.CharField(max_length=10, db_column="unit", verbose_name="计量单位") + qty = models.IntegerField(db_column="qty", verbose_name="采购数量") + is_bom = models.IntegerField(db_column="IsBom", verbose_name="是否成本结构") + unit_price = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="unit_price", + null=True, + blank=True, + verbose_name="标准品单价", + ) + product_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="product_cost", + null=True, + blank=True, + verbose_name="标准品总价", + ) + total_material_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_material_cost", + null=True, + blank=True, + verbose_name="材料成本合计", + ) + total_processing_cost = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_processing_cost", + null=True, + blank=True, + verbose_name="加工成本合计", + ) + total_other_expense = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_other_expense", + null=True, + blank=True, + verbose_name="其他费用合计", + ) + total_opex_amt = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_opex_amt", + null=True, + blank=True, + verbose_name="管销研费用", + ) + profit_rate = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="profit_rate", + null=True, + blank=True, + verbose_name="利润", + ) + total_price_excl_tax = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_price_excl_tax", + null=True, + blank=True, + verbose_name="不含税总价", + ) + tax_rate = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="tax_rate", + null=True, + blank=True, + verbose_name="税费", + ) + total_price_incl_tax = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="total_price_incl_tax", + null=True, + blank=True, + verbose_name="含税总价", + ) + is_awarded = models.IntegerField( + choices=ITEM_AWARD_STATUS_CHOICES, + db_column="is_awarded", + null=True, + blank=True, + verbose_name="报价中标否", + ) + winning_bid_price = models.DecimalField( + max_digits=12, + decimal_places=4, + db_column="winning_bid_price", + null=True, + blank=True, + verbose_name="中标价格", + ) + + class Meta: + db_table = table_prefix + "misc_sup_quot_items" + verbose_name = "杂采报价单-上阶物料明细表" + verbose_name_plural = verbose_name + ordering = ("autoid",) + indexes = [ + models.Index(fields=["part_id"]), + ] + + def __str__(self): + return f"{self.quotation_no_id}-{self.part_id}" diff --git a/backend/apps/pissupplier/serializers.py b/backend/apps/pissupplier/serializers.py new file mode 100644 index 0000000..6895777 --- /dev/null +++ b/backend/apps/pissupplier/serializers.py @@ -0,0 +1,773 @@ +import json +from decimal import Decimal +from typing import Optional + +from django.db import models, transaction +from django.utils import timezone +from rest_framework import serializers + +from dvadmin.utils.serializers import CustomModelSerializer +from apps.pisadmin.basicinfo.models import Company +from apps.pisadmin.miscprocurement.models import ( + CostEstimateTemplateBody, + CostEstimateTemplateHead, + Inquiry, + InquiryAttachment, +) +from apps.pissupplier.models import ( + QuotationMaster, + QuotationAttachment, + QuotationMaterial, + QuotationProcess, + QuotationOther, + QuotationProfit, + QuotationItem, +) + + +class BusinessAuditSerializer(CustomModelSerializer): + + audit_create_user_field = None + audit_create_time_field = None + audit_update_user_field = None + audit_update_time_field = None + audit_datetime_format = "%Y-%m-%d %H:%M:%S" + + def _has_serializer_field(self, field_name): + return bool(field_name) and field_name in self.fields + + def _get_model_field(self, field_name): + if not field_name: + return None + try: + return self.Meta.model._meta.get_field(field_name) + except Exception: + return None + + def _resolve_time_value(self, field_name, now): + model_field = self._get_model_field(field_name) + if isinstance(model_field, models.DateTimeField): + return now + return now.strftime(self.audit_datetime_format) + + def _apply_business_audit(self, validated_data, *, is_create): + username = self.get_request_username() + if not username: + return validated_data + + now = timezone.now() + create_user_field = self.audit_create_user_field + create_time_field = self.audit_create_time_field + update_user_field = self.audit_update_user_field + update_time_field = self.audit_update_time_field + + if is_create: + if self._has_serializer_field(create_user_field) and not validated_data.get(create_user_field): + validated_data[create_user_field] = username + if self._has_serializer_field(create_time_field) and not validated_data.get(create_time_field): + validated_data[create_time_field] = self._resolve_time_value(create_time_field, now) + + if self._has_serializer_field(update_user_field): + validated_data[update_user_field] = username + if self._has_serializer_field(update_time_field): + validated_data[update_time_field] = self._resolve_time_value(update_time_field, now) + + return validated_data + + def create(self, validated_data): + validated_data = self._apply_business_audit(validated_data, is_create=True) + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data = self._apply_business_audit(validated_data, is_create=False) + return super().update(instance, validated_data) + + +# 与采购端 rfqmiscellaneous `buildSectionsFromCostTemplate` 一致,供供应商报价页展示列与只读规则 +_COST_CATEGORY_TO_TITLE = { + "1": "材料成本", + "2": "加工成本", + "3": "其它成本", + "4": "管销研费用", + "5": "利润", + "6": "税金", + "7": "产品明细", +} +_COST_SECTION_ORDER = ("产品明细", "材料成本", "加工成本", "其它成本", "管销研费用", "利润", "税金") + + +def build_cost_template_sections_for_quotation(template_no: str, template_version: Optional[int] = None): + """ + 将 `CostEstimateTemplateBody` 扁平行展开为前端 `sections[]`(含 fields[].is_computed / supplier_required)。 + 使用询价单 `Inquiry.template` 作为 template_no,`Inquiry.template_version` 锁定主表 version。 + + template_version 为 None 时兼容旧数据:取该编号下已确认主表的最高 version。 + """ + if not template_no: + return [] + tn = str(template_no).strip() + base_qs = CostEstimateTemplateHead.objects.filter(template_no=tn, status=1) + head = None + if template_version is not None and template_version != "": + try: + ver = int(template_version) + except (TypeError, ValueError): + ver = None + if ver is not None: + head = base_qs.filter(version=ver).first() + if head is None: + head = base_qs.order_by("-version", "-id").first() + if not head: + return [] + items = list( + CostEstimateTemplateBody.objects.filter(template_no=head.template_no, version=head.version).order_by( + "item_order", "id" + ) + ) + group = {} + for it in items: + cat = str(it.cost_category or "") + title = _COST_CATEGORY_TO_TITLE.get(cat, "其它成本") + group.setdefault(title, []).append(it) + + def _field_dict(it): + ic = int(getattr(it, "is_computed", 0) or 0) + sr = int(getattr(it, "supplier_required", 0) or 0) + pr = int(getattr(it, "purchaser_required", 0) or 0) + return { + "key": it.item_no or "", + "label": it.item_name_cn or it.item_no or "", + "is_computed": ic, + "supplier_required": sr, + "purchaser_required": pr, + "autoFill": ic == 1, + "purchaserRequired": pr == 1, + } + + sections = [] + for title in _COST_SECTION_ORDER: + rows = group.get(title) + if not rows: + continue + if title == "材料成本": + supplier_can_add = int(getattr(head, "is_can_add_materials", 0) or 0) == 1 + elif title == "加工成本": + supplier_can_add = int(getattr(head, "is_can_add_process", 0) or 0) == 1 + else: + supplier_can_add = False + sections.append( + { + "id": title, + "title": title, + "name": title, + "enabled": True, + "supplierCanAddRow": supplier_can_add, + "fields": [_field_dict(r) for r in rows], + } + ) + return sections + + +def normalize_option_json(payload): + if payload in (None, ""): + return None + if isinstance(payload, str): + text = payload.strip() + return text or None + try: + return json.dumps(payload, ensure_ascii=False) + except (TypeError, ValueError): + return None + + +class QuotationAttachmentSerializer(BusinessAuditSerializer): + uploadtime = serializers.CharField(required=False, allow_blank=True, allow_null=True) + uploaduser = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=20) + + audit_create_user_field = "uploaduser" + audit_create_time_field = "uploadtime" + + class Meta: + model = QuotationAttachment + fields = [ + "autoid", + "quotation_no", + "part_id", + "file_name", + "file_path", + "uploadtime", + "uploaduser", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + +class NestedQuotationAttachmentSerializer(QuotationAttachmentSerializer): + class Meta(QuotationAttachmentSerializer.Meta): + validators = [] + + +class QuotationMaterialSerializer(serializers.ModelSerializer): + class Meta: + model = QuotationMaterial + fields = [ + "autoid", + "quotation_no", + "part_id", + "material_spec", + "length", + "width", + "height", + "unit_price", + "qty", + "specific_gravity", + "material_cost", + "weight", + "remark", + "option_json", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + def validate_option_json(self, value): + return normalize_option_json(value) + + +class NestedQuotationMaterialSerializer(QuotationMaterialSerializer): + class Meta(QuotationMaterialSerializer.Meta): + validators = [] + + +class QuotationProcessSerializer(serializers.ModelSerializer): + class Meta: + model = QuotationProcess + fields = [ + "autoid", + "quotation_no", + "part_id", + "process_station", + "unit", + "unit_rate", + "process_qty", + "process_price", + "remark", + "option_json", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + def validate_option_json(self, value): + return normalize_option_json(value) + + +class NestedQuotationProcessSerializer(QuotationProcessSerializer): + class Meta(QuotationProcessSerializer.Meta): + validators = [] + + +class QuotationOtherSerializer(serializers.ModelSerializer): + """报价单其它成本子表:按料号维度存包装费、运输费;采购端比价展开「其它成本」明细与此字段一致。""" + + class Meta: + model = QuotationOther + fields = [ + "autoid", + "quotation_no", + "part_id", + "packaging_cost", + "transportation_cost", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + +class NestedQuotationOtherSerializer(QuotationOtherSerializer): + class Meta(QuotationOtherSerializer.Meta): + validators = [] + + +class QuotationProfitSerializer(serializers.ModelSerializer): + class Meta: + model = QuotationProfit + fields = [ + "autoid", + "quotation_no", + "part_id", + "tax_rate", + "profit_rate", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + +class NestedQuotationProfitSerializer(QuotationProfitSerializer): + class Meta(QuotationProfitSerializer.Meta): + validators = [] + + +class QuotationItemSerializer(serializers.ModelSerializer): + class Meta: + model = QuotationItem + fields = [ + "autoid", + "quotation_no", + "part_id", + "product_name", + "unit", + "qty", + "is_bom", + "unit_price", + "product_cost", + "total_material_cost", + "total_processing_cost", + "total_other_expense", + "total_opex_amt", + "profit_rate", + "total_price_excl_tax", + "tax_rate", + "total_price_incl_tax", + "is_awarded", + "winning_bid_price", + ] + read_only_fields = ["autoid"] + extra_kwargs = {"quotation_no": {"required": False, "allow_null": True}} + + +class NestedQuotationItemSerializer(QuotationItemSerializer): + class Meta(QuotationItemSerializer.Meta): + validators = [] + + +_INQUIRY_ATTACHMENT_TYPE_LABELS = {1: "产品图纸", 2: "招标文件", 3: "其它文件"} + + +class SupplierInquiryAttachmentSerializer(serializers.ModelSerializer): + """供应商报价详情:询价单附件(`pis_proc_inquiry_attachment`),只读。""" + + file_type_label = serializers.SerializerMethodField() + + def get_file_type_label(self, obj): + ft = getattr(obj, "file_type", None) + try: + ft = int(ft) + except (TypeError, ValueError): + ft = 3 + return _INQUIRY_ATTACHMENT_TYPE_LABELS.get(ft, "其它文件") + + class Meta: + model = InquiryAttachment + fields = [ + "id", + "part_id", + "file_type", + "file_type_label", + "file_name", + "file_path", + "upload_time", + "upload_user", + ] + read_only_fields = [ + "id", + "part_id", + "file_type", + "file_name", + "file_path", + "upload_time", + "upload_user", + ] + + +class QuotationMasterSerializer(BusinessAuditSerializer): + """杂采报价单主表序列化器 + + 详情接口返回结构供采购端「比价」弹窗中点击供应商预览整单(材料/加工/其它/利润/上阶物料行等)。 + + `status`:1 待报价、2 报价中、3 已报价、4 已过期;超时由 POST `quotation_master/sync_expired/` 批量更新。 + 同步过期后,若询价单 `Inquiry` 仍为「发布」(3) 或「报价中」(4) 且该询价下已无任何待报价/报价中单, + 则询价单会置为「报价结束」(5),见 `Inquiry.sync_to_quote_closed_when_no_open_quotations`。 + 全部供应商名单均已正式提交报价(均为已报价)时由 `POST …/submit/` 内 `Inquiry.sync_to_quote_closed_when_all_suppliers_quoted` 收口。 + """ + + creattime = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + quote_deadline = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + bid_start_time = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + bid_end_time = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + # 列表/详情:询价主表 company_code + 公司信息简称(同请求内按 inquiry_no 缓存,减轻重复查询) + inquiry_company_code = serializers.SerializerMethodField(read_only=True) + inquiry_company_short_name = serializers.SerializerMethodField(read_only=True) + # 列表/详情展示:上阶物料明细 total_price_incl_tax 之和(主表无报价金额列) + quote_amount = serializers.SerializerMethodField(read_only=True) + template_sections = serializers.SerializerMethodField(read_only=True) + inquiry_attachments = serializers.SerializerMethodField(read_only=True) + attachments = QuotationAttachmentSerializer(many=True, required=False) + material_costs = QuotationMaterialSerializer(many=True, required=False) + process_costs = NestedQuotationProcessSerializer(many=True, required=False) + other_costs = QuotationOtherSerializer(many=True, required=False) + profit_costs = QuotationProfitSerializer(many=True, required=False) + rfq_items = QuotationItemSerializer(many=True, required=False) + + audit_create_user_field = "createuser" + audit_create_time_field = "creattime" + audit_update_user_field = "quoteuser" + audit_update_time_field = "quotetime" + + def _inquiry_plant_tuple(self, inquiry_no): + """(company_code, 展示用简称);简称优先 company_short_name,否则 company_name,再否则代码。""" + if not inquiry_no: + return None, "" + cache = self.context.setdefault("_inquiry_plant_by_inquiry_no", {}) + if inquiry_no in cache: + return cache[inquiry_no] + inq = Inquiry.objects.filter(inquiry_no=inquiry_no).only("company_code").first() + code = (getattr(inq, "company_code", None) or "").strip() if inq else "" + if not code: + cache[inquiry_no] = (None, "") + return cache[inquiry_no] + co = Company.objects.filter(company_code=code).only("company_short_name", "company_name").first() + short = "" + if co: + short = (co.company_short_name or co.company_name or "").strip() + if not short: + short = code + cache[inquiry_no] = (code, short) + return cache[inquiry_no] + + def get_inquiry_company_code(self, obj): + """与 `Inquiry.company_code` 一致;供应商端拉取杂采材质/工站列表时须按此精确过滤厂区。""" + code, _ = self._inquiry_plant_tuple(getattr(obj, "inquiry_no", None)) + return code + + def get_inquiry_company_short_name(self, obj): + _, short = self._inquiry_plant_tuple(getattr(obj, "inquiry_no", None)) + return short or "" + + def get_quote_amount(self, obj): + total = Decimal("0") + has_value = False + for row in obj.rfq_items.all(): + v = getattr(row, "total_price_incl_tax", None) + if v is not None: + total += v + has_value = True + return total if has_value else None + + def get_template_sections(self, obj): + """列表接口不展开,避免 N+1;详情(retrieve)返回与询价成本模板一致的 sections。""" + if self.context.get("quotation_skip_template_sections"): + return [] + if self.context.get("view_action") != "retrieve": + return [] + inquiry_no = getattr(obj, "inquiry_no", None) + if not inquiry_no: + return [] + inq = Inquiry.objects.filter(inquiry_no=inquiry_no).only("template", "template_version").first() + if not inq or not inq.template: + return [] + return build_cost_template_sections_for_quotation( + inq.template, + getattr(inq, "template_version", None), + ) + + def get_inquiry_attachments(self, obj): + """详情接口返回询价单附件;列表不查,避免 N+1。""" + if self.context.get("view_action") != "retrieve": + return [] + inquiry_no = getattr(obj, "inquiry_no", None) + if not inquiry_no: + return [] + qs = ( + InquiryAttachment.objects.filter(inquiry_no=inquiry_no) + .order_by("file_type", "-upload_time", "id") + ) + return SupplierInquiryAttachmentSerializer(qs, many=True).data + + class Meta: + model = QuotationMaster + fields = "__all__" + read_only_fields = ["autoid"] + + +class QuotationMasterCreateUpdateSerializer(BusinessAuditSerializer): + """杂采报价单主表写入序列化器""" + + attachments = NestedQuotationAttachmentSerializer(many=True, required=False) + material_costs = NestedQuotationMaterialSerializer(many=True, required=False) + process_costs = NestedQuotationProcessSerializer(many=True, required=False) + other_costs = NestedQuotationOtherSerializer(many=True, required=False) + profit_costs = NestedQuotationProfitSerializer(many=True, required=False) + rfq_items = NestedQuotationItemSerializer(many=True, required=False) + quotation_no = serializers.CharField(max_length=20) + inquiry_no = serializers.CharField(max_length=20) + supplier_code = serializers.CharField(max_length=20) + supplier_name = serializers.CharField(max_length=20) + contact_person = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True) + contact_phone = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True) + contact_email = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True) + quote_deadline = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", + required=False, + allow_null=True, + ) + validity_days = serializers.IntegerField(required=False, allow_null=True) + delivery_days = serializers.IntegerField(required=False, allow_null=True) + payment_method = serializers.IntegerField(required=False, allow_null=True) + status = serializers.IntegerField(required=False, allow_null=True) + is_awarded = serializers.IntegerField(required=False, allow_null=True) + createuser = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True) + creattime = serializers.DateTimeField(required=False, allow_null=True) + quoteuser = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True) + quotetime = serializers.DateTimeField(required=False, allow_null=True) + remark = serializers.CharField(required=False, allow_blank=True, allow_null=True) + buying_method = serializers.IntegerField(required=False, allow_null=True) + bid_start_time = serializers.DateTimeField(required=False, allow_null=True) + bid_end_time = serializers.DateTimeField(required=False, allow_null=True) + + audit_create_user_field = "createuser" + audit_create_time_field = "creattime" + audit_update_user_field = "quoteuser" + audit_update_time_field = "quotetime" + + class Meta: + model = QuotationMaster + fields = "__all__" + # 创建人/创建时间仅发布生成或首次创建时由服务端写入;禁止 PUT 用详情回传覆盖 + read_only_fields = ["autoid", "createuser", "creattime"] + + def _apply_business_audit(self, validated_data, *, is_create): + validated_data = super()._apply_business_audit(validated_data, is_create=is_create) + # 报价时间仅通过视图 `submit` 写入;保存/新建接口不自动写入 quotetime + tf = self.audit_update_time_field + if tf and tf in validated_data: + validated_data.pop(tf, None) + return validated_data + + def validate_payment_method(self, value): + if value is None: + return value + valid_values = {choice[0] for choice in QuotationMaster.PAYMENT_METHOD_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("付款方式不合法") + return value + + def validate_status(self, value): + if value is None: + return value + valid_values = {choice[0] for choice in QuotationMaster.STATUS_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("状态值不合法") + return value + + def validate_is_awarded(self, value): + if value is None: + return value + valid_values = {choice[0] for choice in QuotationMaster.AWARD_STATUS_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("中标状态值不合法") + return value + + def validate_buying_method(self, value): + if value is None: + return value + valid_values = {choice[0] for choice in QuotationMaster.BUYING_METHOD_CHOICES} + if value not in valid_values: + raise serializers.ValidationError("采购方式(寻源方式)取值不合法") + return value + + def _upsert_attachments(self, quotation, attachments): + QuotationAttachment.objects.filter(quotation_no=quotation).delete() + if not attachments: + return + bulk = [] + username = self.get_request_username() + now_text = timezone.now().strftime(self.audit_datetime_format) + fn_max = QuotationAttachment._meta.get_field("file_name").max_length + fp_max = QuotationAttachment._meta.get_field("file_path").max_length + part_max = QuotationAttachment._meta.get_field("part_id").max_length + for row in attachments: + fn = (row.get("file_name") or "").strip() + fp = row.get("file_path") + fp = (fp or "").strip() if fp is not None else "" + pid = (row.get("part_id") or "").strip() + if not fn and not fp: + continue + bulk.append( + QuotationAttachment( + quotation_no=quotation, + part_id=pid[:part_max], + file_name=fn[:fn_max], + file_path=(fp[:fp_max] if fp else None) or None, + uploadtime=row.get("uploadtime") or now_text, + uploaduser=row.get("uploaduser") or username or None, + ) + ) + if bulk: + QuotationAttachment.objects.bulk_create(bulk) + + def _upsert_material_costs(self, quotation, material_costs): + QuotationMaterial.objects.filter(quotation_no=quotation).delete() + if not material_costs: + return + bulk = [] + for row in material_costs: + bulk.append( + QuotationMaterial( + quotation_no=quotation, + part_id=row.get("part_id", ""), + material_spec=row.get("material_spec", ""), + length=row.get("length"), + width=row.get("width"), + height=row.get("height"), + unit_price=row.get("unit_price"), + qty=row.get("qty"), + specific_gravity=row.get("specific_gravity"), + material_cost=row.get("material_cost"), + weight=row.get("weight"), + remark=row.get("remark"), + option_json=normalize_option_json(row.get("option_json")), + ) + ) + QuotationMaterial.objects.bulk_create(bulk) + + def _upsert_process_costs(self, quotation, process_costs): + QuotationProcess.objects.filter(quotation_no=quotation).delete() + if not process_costs: + return + bulk = [] + for row in process_costs: + bulk.append( + QuotationProcess( + quotation_no=quotation, + part_id=row.get("part_id", ""), + process_station=row.get("process_station"), + unit=row.get("unit"), + unit_rate=row.get("unit_rate"), + process_qty=row.get("process_qty"), + process_price=row.get("process_price"), + remark=row.get("remark"), + option_json=normalize_option_json(row.get("option_json")), + ) + ) + QuotationProcess.objects.bulk_create(bulk) + + def _upsert_other_costs(self, quotation, other_costs): + QuotationOther.objects.filter(quotation_no=quotation).delete() + if not other_costs: + return + bulk = [] + for row in other_costs: + bulk.append( + QuotationOther( + quotation_no=quotation, + part_id=row.get("part_id", ""), + packaging_cost=row.get("packaging_cost"), + transportation_cost=row.get("transportation_cost"), + ) + ) + QuotationOther.objects.bulk_create(bulk) + + def _upsert_profit_costs(self, quotation, profit_costs): + QuotationProfit.objects.filter(quotation_no=quotation).delete() + if not profit_costs: + return + bulk = [] + for row in profit_costs: + bulk.append( + QuotationProfit( + quotation_no=quotation, + part_id=row.get("part_id", ""), + tax_rate=row.get("tax_rate"), + profit_rate=row.get("profit_rate"), + ) + ) + QuotationProfit.objects.bulk_create(bulk) + + def _upsert_rfq_items(self, quotation, rfq_items): + QuotationItem.objects.filter(quotation_no=quotation).delete() + if not rfq_items: + return + bulk = [] + for row in rfq_items: + bulk.append( + QuotationItem( + quotation_no=quotation, + part_id=row.get("part_id", ""), + product_name=row.get("product_name", ""), + unit=row.get("unit", ""), + qty=row.get("qty"), + is_bom=row.get("is_bom"), + unit_price=row.get("unit_price"), + product_cost=row.get("product_cost"), + total_material_cost=row.get("total_material_cost"), + total_processing_cost=row.get("total_processing_cost"), + total_other_expense=row.get("total_other_expense"), + total_opex_amt=row.get("total_opex_amt"), + profit_rate=row.get("profit_rate"), + total_price_excl_tax=row.get("total_price_excl_tax"), + tax_rate=row.get("tax_rate"), + total_price_incl_tax=row.get("total_price_incl_tax"), + is_awarded=row.get("is_awarded"), + winning_bid_price=row.get("winning_bid_price"), + ) + ) + QuotationItem.objects.bulk_create(bulk) + + @transaction.atomic + def create(self, validated_data): + validated_data.pop("status", None) + validated_data.pop("quotetime", None) + attachments = validated_data.pop("attachments", []) + material_costs = validated_data.pop("material_costs", []) + process_costs = validated_data.pop("process_costs", []) + other_costs = validated_data.pop("other_costs", []) + profit_costs = validated_data.pop("profit_costs", []) + rfq_items = validated_data.pop("rfq_items", []) + quotation = super().create(validated_data) + self._upsert_attachments(quotation, attachments) + self._upsert_material_costs(quotation, material_costs) + self._upsert_process_costs(quotation, process_costs) + self._upsert_other_costs(quotation, other_costs) + self._upsert_profit_costs(quotation, profit_costs) + self._upsert_rfq_items(quotation, rfq_items) + return quotation + + @transaction.atomic + def update(self, instance, validated_data): + # 状态与报价时间仅能通过 `QuotationMasterViewSet.submit` 变更 + validated_data.pop("status", None) + validated_data.pop("quotetime", None) + attachments = validated_data.pop("attachments", None) + material_costs = validated_data.pop("material_costs", None) + process_costs = validated_data.pop("process_costs", None) + other_costs = validated_data.pop("other_costs", None) + profit_costs = validated_data.pop("profit_costs", None) + rfq_items = validated_data.pop("rfq_items", None) + quotation = super().update(instance, validated_data) + if attachments is not None: + self._upsert_attachments(quotation, attachments) + if material_costs is not None: + self._upsert_material_costs(quotation, material_costs) + if process_costs is not None: + self._upsert_process_costs(quotation, process_costs) + if other_costs is not None: + self._upsert_other_costs(quotation, other_costs) + if profit_costs is not None: + self._upsert_profit_costs(quotation, profit_costs) + if rfq_items is not None: + self._upsert_rfq_items(quotation, rfq_items) + return quotation diff --git a/backend/apps/pissupplier/urls.py b/backend/apps/pissupplier/urls.py new file mode 100644 index 0000000..6959591 --- /dev/null +++ b/backend/apps/pissupplier/urls.py @@ -0,0 +1,23 @@ +from rest_framework import routers + +from apps.pissupplier.views import ( + QuotationMasterViewSet, + QuotationAttachmentViewSet, + QuotationMaterialViewSet, + QuotationProcessViewSet, + QuotationOtherViewSet, + QuotationProfitViewSet, + QuotationItemViewSet, +) + +router = routers.SimpleRouter() +router.register(r"quotation_master", QuotationMasterViewSet) +router.register(r"quotation_attachment", QuotationAttachmentViewSet) +router.register(r"quotation_material", QuotationMaterialViewSet) +router.register(r"quotation_process", QuotationProcessViewSet) +router.register(r"quotation_other", QuotationOtherViewSet) +router.register(r"quotation_profit", QuotationProfitViewSet) +router.register(r"quotation_item", QuotationItemViewSet) + +urlpatterns = [] +urlpatterns += router.urls diff --git a/backend/apps/pissupplier/views.py b/backend/apps/pissupplier/views.py new file mode 100644 index 0000000..07d3727 --- /dev/null +++ b/backend/apps/pissupplier/views.py @@ -0,0 +1,455 @@ +import logging +from typing import Optional + +from django.db import transaction +from django.db.models import Q +from django.utils import timezone +from rest_framework.decorators import action + +from apps.pisadmin.basicinfo.models import SupplierUser +from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse +from dvadmin.utils.viewset import CustomModelViewSet + +from apps.pisadmin.miscprocurement.models import Inquiry, RFQOperationLogs +from apps.pissupplier.models import ( + QuotationMaster, + QuotationAttachment, + QuotationMaterial, + QuotationProcess, + QuotationOther, + QuotationProfit, + QuotationItem, +) +from apps.pisadmin.basicinfo.views.email_utils import notify_purchasers_quote_timeout_for_inquiries +from apps.pissupplier.serializers import ( + QuotationMasterSerializer, + QuotationMasterCreateUpdateSerializer, + QuotationAttachmentSerializer, + QuotationMaterialSerializer, + QuotationProcessSerializer, + QuotationOtherSerializer, + QuotationProfitSerializer, + QuotationItemSerializer, +) + +logger = logging.getLogger(__name__) + + +def supplier_codes_for_request_user(user) -> Optional[list]: + """ + 若当前登录账号在「供应商用户」表中有有效记录,则返回其 supplier_id 列表(与报价单 supplier_code 一致); + 返回 None 表示不按供应商过滤(采购端等无供应商主数据绑定的账号)。 + 返回 [] 表示应限制为「无可见报价单」(有绑定但无有效 supplier_id)。 + """ + if not user or not user.is_authenticated: + return None + if getattr(user, "is_superuser", False): + return None + un = (getattr(user, "username", None) or "").strip() + em = (getattr(user, "email", None) or "").strip() + q = Q() + if un: + q |= Q(user_email__iexact=un) + if em: + q |= Q(user_email__iexact=em) + if not q: + return None + qs = SupplierUser.objects.filter(q).filter(status=1) + if not qs.exists(): + return None + codes = [] + for raw in qs.values_list("supplier_id", flat=True).distinct(): + if raw is None: + continue + s = str(raw).strip() + if s: + codes.append(s) + return codes if codes else [] + + +def apply_supplier_quotation_scope(qs, user, *, child_via_quotation_no: bool = False): + """供应商门户:仅可访问本供应商报价单主表及子表;采购端无 SupplierUser 匹配时不缩小范围。""" + codes = supplier_codes_for_request_user(user) + if codes is None: + return qs + if not codes: + return qs.none() + if child_via_quotation_no: + return qs.filter(quotation_no__supplier_code__in=codes) + return qs.filter(supplier_code__in=codes) + + +class SupplierQuotationScopeMixin: + """报价单相关接口:登录账号若对应供应商用户主数据,则按 supplier_code 隔离数据。""" + + supplier_scope_via_child_quotation_no = False + + def get_queryset(self): + qs = super().get_queryset() + return apply_supplier_quotation_scope( + qs, + self.request.user, + child_via_quotation_no=self.supplier_scope_via_child_quotation_no, + ) + + +def _supplier_bidding_window_error(instance: QuotationMaster): + """招标:报价/提交仅允许在投标开始时间~投标截止时间(含端点)内。""" + bm = getattr(instance, "buying_method", None) + if bm != 2: + return None + now = timezone.now() + bs = getattr(instance, "bid_start_time", None) + be = getattr(instance, "bid_end_time", None) + if bs is None or be is None: + return ErrorResponse(msg="招标项目未设置投标开始或截止时间,无法报价或提交") + # if now < bs: + # return ErrorResponse(msg="投标尚未开始") + if now > be: + return ErrorResponse(msg="已超过投标截止时间") + return None + + +class QuotationMasterViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单主表管理接口 + + 详情 GET 与采购端比价弹窗「供应商报价预览」共用:返回 `QuotationMasterSerializer` 及嵌套材料/加工/其它(包装费、运输费)/利润/上阶物料等。 + 供应商门户账号(在「供应商用户」中有绑定)仅可访问本供应商 supplier_code 下的报价单。 + """ + + queryset = QuotationMaster.objects.prefetch_related( + "rfq_items", + "material_costs", + "process_costs", + "other_costs", + "profit_costs", + ) + serializer_class = QuotationMasterSerializer + create_serializer_class = QuotationMasterCreateUpdateSerializer + update_serializer_class = QuotationMasterCreateUpdateSerializer + + def get_serializer_context(self): + ctx = super().get_serializer_context() + ctx["view_action"] = getattr(self, "action", None) + return ctx + filter_fields = ( + "quotation_no", + "inquiry_no", + "supplier_code", + "supplier_name", + "status", + "is_awarded", + ) + search_fields = ( + "quotation_no", + "inquiry_no", + "supplier_code", + "supplier_name", + "contact_person", + "quoteuser", + "remark", + ) + ordering = ("-creattime", "-autoid") + + @action(methods=["post"], detail=False, url_path="sync_expired") + def sync_expired(self, request): + """ + 将当前用户数据权限范围内、状态为待报价(1)或报价中(2)、且已超过报价截止时间的报价单 + 更新为已过期(4)。若某询价单下已无任何待报价/报价中的报价单,且询价单仍为「发布」或「报价中」, + 则将该询价单置为「报价结束」(5)。供供应商端列表加载前调用。 + """ + now = timezone.now() + qs = self.filter_queryset(self.get_queryset()).filter( + status__in=(1, 2), + quote_deadline__isnull=False, + quote_deadline__lt=now, + ) + quotation_rows = list(qs.values("quotation_no", "inquiry_no", "status", "supplier_name")) + inquiry_nos = list({str(r.get("inquiry_no") or "").strip() for r in quotation_rows if r.get("inquiry_no")}) + inquiry_purchase_type = {} + if inquiry_nos: + inquiry_purchase_type = { + x.inquiry_no: int(x.purchase_type) + for x in Inquiry.objects.filter(inquiry_no__in=inquiry_nos).only("inquiry_no", "purchase_type") + } + username = getattr(getattr(request, "user", None), "username", None) + with transaction.atomic(): + updated = qs.update(status=4) + inquiries_closed = Inquiry.sync_to_quote_closed_when_no_open_quotations( + inquiry_nos, + actor_username=username, + ) + quote_timeout_emails_sent = 0 + if updated: + try: + quote_timeout_emails_sent = notify_purchasers_quote_timeout_for_inquiries(inquiry_nos) + except Exception: + logger.exception("询价截止提醒邮件批量通知失败") + return SuccessResponse( + data={ + "updated": updated, + "inquiries_closed": inquiries_closed, + "quote_timeout_emails_sent": quote_timeout_emails_sent, + }, + msg="同步成功", + ) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + def update(self, request, *args, **kwargs): + instance = self.get_object() + # if instance.status not in (1, 2): + # return ErrorResponse(msg="仅未报价/报价中状态可保存报价内容") + # 含已报价(3):采购端比价窗口可回写中标/议价等(仍不可改 status/quotetime,由序列化器剥离) + if instance.status not in (1, 2, 3): + return ErrorResponse(msg="当前报价单状态不允许保存") + return super().update(request, *args, **kwargs) + + def partial_update(self, request, *args, **kwargs): + instance = self.get_object() + # if instance.status not in (1, 2): + # return ErrorResponse(msg="仅未报价/报价中状态可保存报价内容") + if instance.status not in (1, 2, 3): + return ErrorResponse(msg="当前报价单状态不允许保存") + return super().partial_update(request, *args, **kwargs) + + @staticmethod + def _sync_inquiry_when_quotation_quoting( + inquiry_no: str, + *, + actor_username: Optional[str] = None, + quotation_no: Optional[str] = None, + ): + """ + 任意报价单进入「报价中」(status=2) 时,若询价单仍为「发布」(3),同步为「报价中」(4)。 + 已进入报价中(4) 的询价单无需再改;不回退报价结束及之后状态。 + """ + inq_no = (inquiry_no or "").strip() + if not inq_no: + return + inq = Inquiry.objects.filter(inquiry_no=inq_no, status=3).first() + if not inq: + return + old_status = int(inq.status if inq.status is not None else 0) + now = timezone.now() + update_user = (str(actor_username).strip()[:20] if actor_username else None) or None + inq.status = 4 + inq.update_time = now + inq.update_datetime = now + if update_user: + inq.update_user = update_user + inq.save(update_fields=["status", "update_time", "update_user", "update_datetime"]) + RFQOperationLogs.try_append( + inquiry_no=inq.inquiry_no, + purchase_type=int(inq.purchase_type), + operation_type=6, + operation_user=update_user, + quotation_no=(quotation_no or "-")[:20], + per_status=old_status, + cur_status=4, + operation_desc="供应商进入报价中,询价单同步为报价中", + ) + + @action(methods=["post"], detail=True, url_path="quote") + def quote(self, request, pk=None): + """进入报价中:写入当前时间为报价时间,状态为报价中(2)。仅未报价(status=1)可报价。""" + instance = self.get_object() + if instance.status not in (1, 2): + return ErrorResponse(msg="仅未报价状态可进入报价中") + dl = getattr(instance, "quote_deadline", None) + if dl is not None and dl < timezone.now(): + return ErrorResponse(msg="已超过报价截止时间") + bid_err = _supplier_bidding_window_error(instance) + if bid_err is not None: + return bid_err + instance.status = 2 + instance.quotetime = timezone.now() + username = getattr(getattr(request, "user", None), "username", None) + if username: + instance.quoteuser = username + with transaction.atomic(): + instance.save(update_fields=["status", "quotetime", "quoteuser"]) + self._sync_inquiry_when_quotation_quoting( + instance.inquiry_no, + actor_username=username, + quotation_no=getattr(instance, "quotation_no", None), + ) + serializer = self.get_serializer(instance) + return DetailResponse(data=serializer.data, msg="报价中状态更新成功") + + @action(methods=["post"], detail=True, url_path="submit") + def submit(self, request, pk=None): + """正式提交报价:写入当前时间为报价时间,状态为已报价(3)。仅报价中(status=2)可提交。 + + 若本次提交后,询价单下受邀供应商均已「已报价」,则 ``Inquiry.sync_to_quote_closed_when_all_suppliers_quoted`` + 将询价单置为「报价结束」,并由该同步逻辑向采购负责人发送 HTML 邮件(模板 ``Quote_ended``), + 不在本 action 内重复发信。 + + 若本次提交**未**触发询价单收口为「报价结束」,则单独写一条操作日志(描述「{供应商名称}供应商提交报价」、询价单前后状态均为报价中); + 若已收口,则仅由 ``sync_to_quote_closed_when_all_suppliers_quoted`` 写一条合并描述(含提交与报价结束),本处不再重复记日志。 + """ + instance = self.get_object() + if instance.status != 2: + return ErrorResponse(msg="仅报价中状态可提交报价") + dl = getattr(instance, "quote_deadline", None) + if dl is not None and dl < timezone.now(): + return ErrorResponse(msg="已超过报价截止时间") + bid_err = _supplier_bidding_window_error(instance) + if bid_err is not None: + return bid_err + instance.status = 3 + instance.quotetime = timezone.now() + username = getattr(getattr(request, "user", None), "username", None) + if username: + instance.quoteuser = username + inquiry_quote_closed = False + with transaction.atomic(): + instance.save(update_fields=["status", "quotetime", "quoteuser"]) + inquiry_quote_closed = Inquiry.sync_to_quote_closed_when_all_suppliers_quoted( + instance.inquiry_no, + actor_username=username, + quotation_no=getattr(instance, "quotation_no", None), + ) + if not inquiry_quote_closed: + inq = Inquiry.objects.filter(inquiry_no=instance.inquiry_no).only("purchase_type").first() + purchase_type = int(inq.purchase_type) if inq else 2 + supplier_name = (getattr(instance, "supplier_name", None) or "").strip() or "—" + RFQOperationLogs.try_append( + inquiry_no=instance.inquiry_no, + purchase_type=purchase_type, + operation_type=6, + operation_user=username, + quotation_no=getattr(instance, "quotation_no", None), + per_status=4, + cur_status=4, + operation_desc=f"供应商({supplier_name})提交报价", + ) + serializer = self.get_serializer(instance) + payload = dict(serializer.data) + payload["inquiry_quote_closed"] = inquiry_quote_closed + msg = "提交成功" + (",询价单已进入报价结束" if inquiry_quote_closed else "") + return DetailResponse(data=payload, msg=msg) + + +class QuotationAttachmentViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单附件管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationAttachment.objects.all() + serializer_class = QuotationAttachmentSerializer + create_serializer_class = QuotationAttachmentSerializer + update_serializer_class = QuotationAttachmentSerializer + filter_fields = ("quotation_no", "part_id", "file_name") + search_fields = ("quotation_no", "part_id", "file_name", "uploaduser") + ordering = ("-autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class QuotationMaterialViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单材料成本明细管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationMaterial.objects.all() + serializer_class = QuotationMaterialSerializer + create_serializer_class = QuotationMaterialSerializer + update_serializer_class = QuotationMaterialSerializer + filter_fields = ("quotation_no", "part_id", "material_spec") + search_fields = ("quotation_no", "part_id", "material_spec", "remark") + ordering = ("autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class QuotationProcessViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单加工成本明细管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationProcess.objects.all() + serializer_class = QuotationProcessSerializer + create_serializer_class = QuotationProcessSerializer + update_serializer_class = QuotationProcessSerializer + filter_fields = ("quotation_no", "part_id", "process_station") + search_fields = ("quotation_no", "part_id", "process_station", "remark") + ordering = ("autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class QuotationOtherViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单其他费用明细管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationOther.objects.all() + serializer_class = QuotationOtherSerializer + create_serializer_class = QuotationOtherSerializer + update_serializer_class = QuotationOtherSerializer + filter_fields = ("quotation_no", "part_id") + search_fields = ("quotation_no", "part_id") + ordering = ("autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class QuotationProfitViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单税率利润明细管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationProfit.objects.all() + serializer_class = QuotationProfitSerializer + create_serializer_class = QuotationProfitSerializer + update_serializer_class = QuotationProfitSerializer + filter_fields = ("quotation_no", "part_id") + search_fields = ("quotation_no", "part_id") + ordering = ("autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() + + +class QuotationItemViewSet(SupplierQuotationScopeMixin, CustomModelViewSet): + """杂采报价单上阶物料明细管理接口""" + + supplier_scope_via_child_quotation_no = True + + queryset = QuotationItem.objects.all() + serializer_class = QuotationItemSerializer + create_serializer_class = QuotationItemSerializer + update_serializer_class = QuotationItemSerializer + filter_fields = ("quotation_no", "part_id", "product_name") + search_fields = ("quotation_no", "part_id", "product_name") + ordering = ("autoid",) + + def perform_create(self, serializer): + serializer.save() + + def perform_update(self, serializer): + serializer.save() diff --git a/backend/conf/env.example.py b/backend/conf/env.example.py new file mode 100644 index 0000000..6d31766 --- /dev/null +++ b/backend/conf/env.example.py @@ -0,0 +1,50 @@ +import os + +from application.settings import BASE_DIR + +# ================================================= # +# *************** mysql数据库 配置 *************** # +# ================================================= # +# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 +# sqlite3 设置 +DATABASE_ENGINE = "django.db.backends.sqlite3" +DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3") + +# 使用mysql时,改为此配置 +# DATABASE_ENGINE = "django.db.backends.mysql" +# DATABASE_NAME = 'django-vue3-admin' # mysql 时使用 + +# 数据库地址 改为自己数据库地址 +DATABASE_HOST = '127.0.0.1' +# # 数据库端口 +DATABASE_PORT = 3306 +# # 数据库用户名 +DATABASE_USER = "root" +# # 数据库密码 +DATABASE_PASSWORD = 'DVADMIN3' + +# 表前缀 +TABLE_PREFIX = "dvadmin_" +# ================================================= # +# ******** redis配置,无redis 可不进行配置 ******** # +# ================================================= # +REDIS_DB = 1 +CELERY_BROKER_DB = 3 +REDIS_PASSWORD = 'DVADMIN3' +REDIS_HOST = '127.0.0.1' +REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' +# ================================================= # +# ****************** 功能 启停 ******************* # +# ================================================= # +DEBUG = True +# 启动登录详细概略获取(通过调用api获取ip详细地址。如果是内网,关闭即可) +ENABLE_LOGIN_ANALYSIS_LOG = True +# 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 +LOGIN_NO_CAPTCHA_AUTH = True +# ================================================= # +# ****************** 其他 配置 ******************* # +# ================================================= # + +ALLOWED_HOSTS = ["*"] +# 列权限中排除App应用 +COLUMN_EXCLUDE_APPS = [] diff --git a/backend/conf/env.py b/backend/conf/env.py new file mode 100644 index 0000000..31a9f55 --- /dev/null +++ b/backend/conf/env.py @@ -0,0 +1,91 @@ +import os + +from application.settings import BASE_DIR + +# ================================================= # +# *************** mysql数据库 配置 *************** # +# ================================================= # +# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库 +# sqlite3 设置 +# DATABASE_ENGINE = "django.db.backends.sqlite3" +# DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3") + +# 使用sqlserver时,改为此配置 +DATABASE_ENGINE = "mssql" +DATABASE_NAME = 'pisdb' + +# 数据库地址 改为自己数据库地址 +DATABASE_HOST = '192.168.80.29' +# # 数据库端口 +DATABASE_PORT = 1433 +# # 数据库用户名 +DATABASE_USER = "test" +# # 数据库密码 +DATABASE_PASSWORD = '123456' + +# 表前缀(小写) +TABLE_PREFIX = "pis_" # Procurement Inquiry System +# ================================================= # +# ******** redis配置,无redis 可不进行配置 ******** # +# ================================================= # +REDIS_DB = 1 +CELERY_BROKER_DB = 3 +REDIS_PASSWORD = 'redis_5pGXn2' +REDIS_HOST = '192.168.80.90' +REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' +# ================================================= # +# ****************** 功能 启停 ******************* # +# ================================================= # +DEBUG = True +# 启动登录详细概略获取(通过调用api获取ip详细地址。如果是内网,关闭即可) +ENABLE_LOGIN_ANALYSIS_LOG = True +# 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 +LOGIN_NO_CAPTCHA_AUTH = True +# ================================================= # +# ****************** 其他 配置 ******************* # +# ================================================= # + +ALLOWED_HOSTS = ["*"] +# 列权限中排除App应用 +COLUMN_EXCLUDE_APPS = [] + + +# ================================================= # +# ****************** 邮件 配置 ******************* # +# ================================================= # +# 发送邮箱地址(From 显示) +EMAIL_FROM = "eip@avc.co" +# SMTP 登录账号(服务器不支持认证,留空) +EMAIL_HOST_USER = "" +# SMTP 登录密码/授权码(服务器不支持认证,留空) +EMAIL_HOST_PASSWORD = "" +# SMTP 服务器地址 +EMAIL_HOST = "mailflow.avc.co" +# SMTP 端口 +EMAIL_PORT = 25 +# 是否使用 SSL(如果走 STARTTLS,将 EMAIL_USE_SSL 设为 False,EMAIL_USE_TLS=True) +# 服务器不支持STARTTLS,使用纯明文连接 +EMAIL_USE_SSL = False +EMAIL_USE_TLS = False + +# ================================================= # +# *************** RustFS 对象存储 配置 *************** # +# ================================================= # +RUSTFS_ENDPOINT = "192.168.80.90:9000" +RUSTFS_ACCESS_KEY = "kqaQFK3BMETpd2TcWBnT" +RUSTFS_SECRET_KEY = "39Ms49eTkMW9E633AjFLcnuyFbgt2H1du4nLzpZe" +RUSTFS_BUCKET = "pis-media" +RUSTFS_SECURE = False # True=HTTPS, False=HTTP +RUSTFS_PATH_PREFIX = "" # 存储路径前缀,可为空 +RUSTFS_REGION = "us-east-1" # RustFS 区域(可自定义) + +# ================================================= # +# *************** EIP 对接(出站 / 配置) *************** # +# ================================================= # +# PIS 主动调用 EIP 时使用(sync 插件 clients/eip_client) +EIP_BASE_URL = os.getenv("EIP_BASE_URL", "").rstrip("/") +EIP_API_KEY = os.getenv("EIP_API_KEY", "") +EIP_TIMEOUT = int(os.getenv("EIP_TIMEOUT", "30")) +# 同步重试(预留) +SYNC_RETRY_MAX = int(os.getenv("SYNC_RETRY_MAX", "3")) +SYNC_RETRY_DELAY = int(os.getenv("SYNC_RETRY_DELAY", "60")) \ No newline at end of file diff --git a/backend/conftest.py b/backend/conftest.py new file mode 100644 index 0000000..edf0766 --- /dev/null +++ b/backend/conftest.py @@ -0,0 +1,34 @@ +import pytest +from rest_framework.test import APIClient + + +@pytest.fixture +def api_client(): + """匿名 API 客户端""" + return APIClient() + + +@pytest.fixture +def authenticate(api_client, admin_user): + """已认证的 API 客户端(管理员)""" + api_client.force_authenticate(user=admin_user) + return api_client + + +@pytest.fixture +def admin_user(db): + """创建管理员用户(每次调用自动 rollback,username 使用 Sequence 确保唯一)""" + from tests.factories.system_factory import UserFactory + + user = UserFactory(admin=True) + return user + + +@pytest.fixture +def normal_user(db): + """创建普通用户""" + from tests.factories.system_factory import UserFactory + + user = UserFactory() + return user + diff --git a/backend/del_migrations.py b/backend/del_migrations.py new file mode 100644 index 0000000..775d1dc --- /dev/null +++ b/backend/del_migrations.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +import os + +exclude = ["venv", ".venv"] # 需要排除的文件目录 +for root, dirs, files in os.walk('.'): + dirs[:] = list(set(dirs) - set(exclude)) + if 'migrations' in dirs: + dir = dirs[dirs.index('migrations')] + for root_j, dirs_j, files_j in os.walk(os.path.join(root, dir)): + for file_k in files_j: + if file_k != '__init__.py': + dst_file = os.path.join(root_j, file_k) + print('删除文件>>> ', dst_file) + os.remove(dst_file) \ No newline at end of file diff --git a/backend/docker_start.sh b/backend/docker_start.sh new file mode 100644 index 0000000..49ffb5e --- /dev/null +++ b/backend/docker_start.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# python manage.py makemigrations +# python manage.py migrate +# python manage.py init -y +uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4 diff --git a/backend/dvadmin/__init__.py b/backend/dvadmin/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/dvadmin/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/dvadmin/system/__init__.py b/backend/dvadmin/system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/admin.py b/backend/dvadmin/system/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/dvadmin/system/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/dvadmin/system/apps.py b/backend/dvadmin/system/apps.py new file mode 100644 index 0000000..8302f72 --- /dev/null +++ b/backend/dvadmin/system/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class SystemConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'dvadmin.system' + + def ready(self): + # 注册信号 + import dvadmin.system.signals # 确保路径正确 diff --git a/backend/dvadmin/system/fixtures/__init__.py b/backend/dvadmin/system/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/fixtures/initSerializer.py b/backend/dvadmin/system/fixtures/initSerializer.py new file mode 100644 index 0000000..f983aea --- /dev/null +++ b/backend/dvadmin/system/fixtures/initSerializer.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- +import os + +from rest_framework import serializers + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +import django + +django.setup() +from dvadmin.system.models import ( + Role, Dept, Users, Menu, MenuButton, + ApiWhiteList, Dictionary, SystemConfig, + RoleMenuPermission, RoleMenuButtonPermission, MenuField +) +from dvadmin.utils.serializers import CustomModelSerializer + + +class UsersInitSerializer(CustomModelSerializer): + """ + 初始化获取数信息(用于生成初始化json文件) + """ + role_key = serializers.SerializerMethodField() + dept_key = serializers.SerializerMethodField() + + def get_dept_key(self, obj): + if obj.dept: + return obj.dept.key + else: + return None + + def get_role_key(self, obj): + if obj.role.all(): + return [role.key for role in obj.role.all()] + else: + return [] + + def save(self, **kwargs): + instance = super().save(**kwargs) + role_key = self.initial_data.get('role_key', []) + role_ids = Role.objects.filter(key__in=role_key).values_list('id', flat=True) + instance.role.set(role_ids) + dept_key = self.initial_data.get('dept_key', None) + dept_id = Dept.objects.filter(key=dept_key).first() + instance.dept = dept_id + instance.save() + return instance + + class Meta: + model = Users + fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type', + 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id', + 'password', 'last_login', 'is_superuser', 'role_key' ,'dept_key'] + read_only_fields = ['id'] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class MenuButtonInitSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + + class Meta: + model = MenuButton + fields = ['id', 'name', 'value', 'api', 'method', 'menu'] + read_only_fields = ["id"] + + +class MenuFieldInitSerializer(CustomModelSerializer): + """ + 初始化列权限-序列化器 + """ + + class Meta: + model = MenuField + fields = ['id', 'menu', 'field_name', 'title', 'model'] + read_only_fields = ["id"] + + +class MenuInitSerializer(CustomModelSerializer): + """ + 递归深度获取数信息(用于生成初始化json文件) + """ + name = serializers.CharField(required=False) + children = serializers.SerializerMethodField() + menu_button = serializers.SerializerMethodField() + menu_field = serializers.SerializerMethodField() + + def get_children(self, obj: Menu): + data = [] + instance = Menu.objects.filter(parent_id=obj.id) + if instance: + serializer = MenuInitSerializer(instance=instance, many=True) + data = serializer.data + return data + + def get_menu_button(self, obj: Menu): + data = [] + instance = obj.menuPermission.order_by('method') + if instance: + data = list(instance.values('name', 'value', 'api', 'method')) + return data + + def get_menu_field(self, obj: Menu): + data = [] + instance = obj.menufield_set.order_by('field_name') + if instance: + data = list(instance.values('field_name', 'title', 'model')) + return data + + def save(self, **kwargs): + instance = super().save(**kwargs) + children = self.initial_data.get('children') + menu_button = self.initial_data.get('menu_button') + menu_field = self.initial_data.get('menu_field') + # 菜单表 + if children: + for menu_data in children: + menu_data['parent'] = instance.id + filter_data = { + "name": menu_data['name'], + "web_path": menu_data['web_path'], + "component": menu_data['component'], + "component_name": menu_data['component_name'], + } + instance_obj = Menu.objects.filter(**filter_data).first() + if instance_obj and not self.initial_data.get('reset'): + continue + serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + # 菜单按钮 + if menu_button: + for menu_button_data in menu_button: + menu_button_data['menu'] = instance.id + filter_data = { + "menu": menu_button_data['menu'], + "value": menu_button_data['value'] + } + instance_obj = MenuButton.objects.filter(**filter_data).first() + serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + # 列权限 + if menu_field: + for field_data in menu_field: + field_data['menu'] = instance.id + filter_data = { + 'menu': field_data['menu'], + 'field_name': field_data['field_name'], + 'model': field_data['model'] + } + instance_obj = MenuField.objects.filter(**filter_data).first() + serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + return instance + + class Meta: + model = Menu + fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status', + 'cache', 'visible', 'parent', 'children', 'menu_button', 'menu_field', 'creator', 'dept_belong_id'] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + read_only_fields = ['id', 'children'] + + +class RoleInitSerializer(CustomModelSerializer): + """ + 初始化获取数信息(用于生成初始化json文件) + """ + + class Meta: + model = Role + fields = ['name', 'key', 'sort', 'status', + 'creator', 'dept_belong_id'] + read_only_fields = ["id"] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class RoleMenuInitSerializer(CustomModelSerializer): + """ + 初始化角色菜单(用于生成初始化json文件) + """ + role__key = serializers.CharField(source='role.key') + menu__web_path = serializers.CharField(source='menu.web_path') + menu__component_name = serializers.CharField(source='menu.component_name', allow_blank=True) + + def update(self, instance, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first() + validated_data['role'] = role_id + validated_data['menu'] = menu_id + return super().update(instance, validated_data) + + + def create(self, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first() + validated_data['role'] = role_id + validated_data['menu'] = menu_id + return super().create(validated_data) + + class Meta: + model = RoleMenuPermission + fields = ['role__key', 'menu__web_path', 'menu__component_name','creator', 'dept_belong_id'] + read_only_fields = ["id"] + extra_kwargs = { + 'role': {'required': False}, + 'menu': {'required': False}, + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class RoleMenuButtonInitSerializer(CustomModelSerializer): + """ + 初始化角色菜单按钮(用于生成初始化json文件) + """ + role__key = serializers.CharField(source='role.key') + menu_button__value = serializers.CharField(source='menu_button.value') + data_range = serializers.CharField(max_length=100, required=False) + + def update(self, instance, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first() + validated_data['role'] = role_id + validated_data['menu_button'] = menu_button_id + instance = super().create(validated_data) + instance.dept.set([]) + return super().update(instance, validated_data) + + def create(self, validated_data): + init_data = self.initial_data + role_id = Role.objects.filter(key=init_data['role__key']).first() + menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first() + validated_data['role'] = role_id + validated_data['menu_button'] = menu_button_id + instance = super().create(validated_data) + instance.dept.set([]) + return instance + + def save(self, **kwargs): + if not self.instance or self.initial_data.get('reset'): + return super().save(**kwargs) + return self.instance + + class Meta: + model = RoleMenuButtonPermission + fields = ['role__key', 'menu_button__value', 'data_range', 'dept', 'creator', 'dept_belong_id'] + read_only_fields = ["id"] + extra_kwargs = { + 'role': {'required': False}, + 'menu': {'required': False}, + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class ApiWhiteListInitSerializer(CustomModelSerializer): + """ + 初始化获取数信息(用于生成初始化json文件) + """ + + class Meta: + model = ApiWhiteList + fields = ['url', 'method', 'enable_datasource', 'creator', 'dept_belong_id'] + read_only_fields = ["id"] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class DeptInitSerializer(CustomModelSerializer): + """ + 递归深度获取数信息(用于生成初始化json文件) + """ + children = serializers.SerializerMethodField() + + def get_children(self, obj: Dept): + data = [] + instance = Dept.objects.filter(parent_id=obj.id) + if instance: + serializer = DeptInitSerializer(instance=instance, many=True) + data = serializer.data + return data + + def save(self, **kwargs): + instance = super().save(**kwargs) + children = self.initial_data.get('children') + if children: + for menu_data in children: + menu_data['parent'] = instance.id + filter_data = { + "name": menu_data['name'], + "parent": menu_data['parent'], + "key": menu_data['key'] + } + instance_obj = Dept.objects.filter(**filter_data).first() + if instance_obj and not self.initial_data.get('reset'): + continue + serializer = DeptInitSerializer(instance_obj, data=menu_data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + + return instance + + class Meta: + model = Dept + fields = ['name', 'sort', 'owner', 'phone', 'email', 'status', 'parent', 'creator', 'dept_belong_id', + 'children', 'key'] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + read_only_fields = ['id', 'children'] + + +class DictionaryInitSerializer(CustomModelSerializer): + """ + 初始化获取数信息(用于生成初始化json文件) + """ + children = serializers.SerializerMethodField() + + def get_children(self, obj: Dictionary): + data = [] + instance = Dictionary.objects.filter(parent_id=obj.id) + if instance: + serializer = DictionaryInitSerializer(instance=instance, many=True) + data = serializer.data + return data + + def save(self, **kwargs): + instance = super().save(**kwargs) + children = self.initial_data.get('children') + # 菜单表 + if children: + for data in children: + data['parent'] = instance.id + filter_data = { + "value": data['value'], + "parent": data['parent'] + } + instance_obj = Dictionary.objects.filter(**filter_data).first() + if instance_obj and not self.initial_data.get('reset'): + continue + serializer = DictionaryInitSerializer(instance_obj, data=data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + return instance + + class Meta: + model = Dictionary + fields = ['label', 'value', 'parent', 'type', 'color', 'is_value', 'status', 'sort', 'remark', 'creator', + 'dept_belong_id', 'children'] + read_only_fields = ["id"] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } + + +class SystemConfigInitSerializer(CustomModelSerializer): + """ + 初始化获取数信息(用于生成初始化json文件) + """ + children = serializers.SerializerMethodField() + + def get_children(self, obj: SystemConfig): + data = [] + instance = SystemConfig.objects.filter(parent_id=obj.id) + if instance: + serializer = SystemConfigInitSerializer(instance=instance, many=True) + data = serializer.data + return data + + def save(self, **kwargs): + instance = super().save(**kwargs) + children = self.initial_data.get('children') + # 菜单表 + if children: + for data in children: + data['parent'] = instance.id + filter_data = { + "key": data['key'], + "parent": data['parent'] + } + instance_obj = SystemConfig.objects.filter(**filter_data).first() + if instance_obj and not self.initial_data.get('reset'): + continue + serializer = SystemConfigInitSerializer(instance_obj, data=data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + return instance + + class Meta: + model = SystemConfig + fields = ['parent', 'title', 'key', 'value', 'sort', 'status', 'data_options', 'form_item_type', 'rule', + 'placeholder', 'setting', 'creator', 'dept_belong_id', 'children'] + read_only_fields = ["id"] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } diff --git a/backend/dvadmin/system/fixtures/init_apiwhitelist.json b/backend/dvadmin/system/fixtures/init_apiwhitelist.json new file mode 100644 index 0000000..e33de57 --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_apiwhitelist.json @@ -0,0 +1,7 @@ +[ + { + "url": "/api/system/dept_lazy_tree/", + "method": 0, + "enable_datasource": true + } +] \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_dept.json b/backend/dvadmin/system/fixtures/init_dept.json new file mode 100644 index 0000000..3603364 --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_dept.json @@ -0,0 +1,36 @@ +[ + { + "name": "DVAdmin团队", + "key": "dvadmin", + "sort": 1, + "owner": "", + "phone": "", + "email": "", + "status": true, + "parent": null, + "children": [ + { + "name": "运营部", + "key": "", + "sort": 2, + "owner": "", + "phone": "", + "email": "", + "status": true, + "parent": 1, + "children": [] + }, + { + "name": "技术部", + "key": "technology", + "sort": 1, + "owner": "", + "phone": "", + "email": "", + "status": true, + "parent": 3, + "children": [] + } + ] + } +] diff --git a/backend/dvadmin/system/fixtures/init_dictionary.json b/backend/dvadmin/system/fixtures/init_dictionary.json new file mode 100644 index 0000000..c4bd186 --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_dictionary.json @@ -0,0 +1,595 @@ +[ + { + "label": "启用/禁用-布尔值", + "value": "button_status_bool", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 1, + "remark": null, + "children": [ + { + "label": "启用", + "value": "true", + "parent": 1, + "type": 6, + "color": "success", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "禁用", + "value": "false", + "parent": 1, + "type": 6, + "color": "danger", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "系统按钮", + "value": "system_button", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 2, + "remark": null, + "children": [ + { + "label": "新增", + "value": "Create", + "parent": 4, + "type": 0, + "color": "success", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "编辑", + "value": "Update", + "parent": 4, + "type": 0, + "color": "primary", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + }, + { + "label": "删除", + "value": "Delete", + "parent": 4, + "type": 0, + "color": "danger", + "is_value": true, + "status": true, + "sort": 3, + "remark": null, + "children": [] + }, + { + "label": "详情", + "value": "Retrieve", + "parent": 4, + "type": 0, + "color": "info", + "is_value": true, + "status": true, + "sort": 4, + "remark": null, + "children": [] + }, + { + "label": "查询", + "value": "Search", + "parent": 4, + "type": 0, + "color": "warning", + "is_value": true, + "status": true, + "sort": 5, + "remark": null, + "children": [] + }, + { + "label": "保存", + "value": "Save", + "parent": 4, + "type": 0, + "color": "success", + "is_value": true, + "status": true, + "sort": 6, + "remark": null, + "children": [] + }, + { + "label": "导入", + "value": "Import", + "parent": 4, + "type": 0, + "color": "primary", + "is_value": true, + "status": true, + "sort": 7, + "remark": null, + "children": [] + }, + { + "label": "导出", + "value": "Export", + "parent": 4, + "type": 0, + "color": "warning", + "is_value": true, + "status": true, + "sort": 8, + "remark": null, + "children": [] + } + ] + }, + { + "label": "启用/禁用-数字值", + "value": "button_status_number", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 3, + "remark": null, + "children": [ + { + "label": "启用", + "value": "1", + "parent": 13, + "type": 1, + "color": "success", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "禁用", + "value": "0", + "parent": 13, + "type": 1, + "color": "danger", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "是/否-布尔值", + "value": "button_whether_bool", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 4, + "remark": null, + "children": [ + { + "label": "是", + "value": "true", + "parent": 16, + "type": 6, + "color": "success", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "否", + "value": "false", + "parent": 16, + "type": 6, + "color": "danger", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "是/否-数字值", + "value": "button_whether_number", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 5, + "remark": null, + "children": [ + { + "label": "是", + "value": "1", + "parent": 19, + "type": 1, + "color": "success", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "否", + "value": "2", + "parent": 19, + "type": 1, + "color": "danger", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "用户类型", + "value": "user_type", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 6, + "remark": null, + "children": [ + { + "label": "后台用户", + "value": "0", + "parent": 22, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "前台用户", + "value": "1", + "parent": 22, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "表单类型", + "value": "config_form_type", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 7, + "remark": null, + "children": [ + { + "label": "text", + "value": "0", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 0, + "remark": null, + "children": [] + }, + { + "label": "textarea", + "value": "3", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 0, + "remark": null, + "children": [] + }, + { + "label": "number", + "value": "10", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 0, + "remark": null, + "children": [] + }, + { + "label": "datetime", + "value": "1", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "date", + "value": "2", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + }, + { + "label": "time", + "value": "15", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 3, + "remark": null, + "children": [] + }, + { + "label": "select", + "value": "4", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 4, + "remark": null, + "children": [] + }, + { + "label": "checkbox", + "value": "5", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 5, + "remark": null, + "children": [] + }, + { + "label": "radio", + "value": "6", + "parent": 25, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 6, + "remark": null, + "children": [] + }, + { + "label": "switch", + "value": "9", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 6, + "remark": null, + "children": [] + }, + { + "label": "文件附件", + "value": "8", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 7, + "remark": null, + "children": [] + }, + { + "label": "图片(单张)", + "value": "7", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 8, + "remark": null, + "children": [] + }, + { + "label": "图片(多张)", + "value": "12", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 9, + "remark": null, + "children": [] + }, + { + "label": "数组", + "value": "11", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 11, + "remark": null, + "children": [] + }, + { + "label": "关联表", + "value": "13", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 13, + "remark": null, + "children": [] + }, + { + "label": "关联表(多选)", + "value": "14", + "parent": 25, + "type": 1, + "color": "", + "is_value": true, + "status": true, + "sort": 14, + "remark": null, + "children": [] + } + ] + }, + { + "label": "性别", + "value": "gender", + "parent": null, + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 8, + "remark": null, + "children": [ + { + "label": "未知", + "value": "0", + "parent": 42, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 0, + "remark": null, + "children": [] + }, + { + "label": "男", + "value": "1", + "parent": 42, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "女", + "value": "2", + "parent": 42, + "type": 1, + "color": null, + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + } + ] + }, + { + "label": "文件存储引擎", + "value": "file_engine", + "type": 0, + "color": null, + "is_value": false, + "status": true, + "sort": 9, + "remark": null, + "children": [ + { + "label": "本地", + "value": "local", + "type": 0, + "color": "primary", + "is_value": true, + "status": true, + "sort": 1, + "remark": null, + "children": [] + }, + { + "label": "阿里云oss", + "value": "oss", + "type": 0, + "color": "success", + "is_value": true, + "status": true, + "sort": 2, + "remark": null, + "children": [] + }, + { + "label": "腾讯cos", + "value": "cos", + "type": 0, + "color": "warning", + "is_value": true, + "status": true, + "sort": 3, + "remark": null, + "children": [] + } + ] + } +] \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_menu.json b/backend/dvadmin/system/fixtures/init_menu.json new file mode 100644 index 0000000..ec9742a --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_menu.json @@ -0,0 +1,1463 @@ +[ + { + "name": "系统管理", + "icon": "iconfont icon-xitongshezhi", + "sort": 1, + "is_link": false, + "is_catalog": true, + "web_path": "/system", + "component": "", + "component_name": "", + "status": true, + "cache": false, + "visible": true, + "children": [ + { + "name": "用户管理", + "icon": "iconfont icon-icon-", + "sort": 1, + "is_link": false, + "is_catalog": false, + "web_path": "/user", + "component": "system/user/index", + "component_name": "user", + "status": true, + "cache": false, + "visible": true, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "user:Search", + "api": "/api/system/user/", + "method": 0 + }, + { + "name": "新增", + "value": "user:Create", + "api": "/api/system/user/", + "method": 1 + }, + { + "name": "编辑", + "value": "user:Update", + "api": "/api/system/user/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "user:Delete", + "api": "/api/system/user/{id}/", + "method": 3 + }, + { + "name": "导出", + "value": "user:Export", + "api": "/api/system/user/export/", + "method": 1 + }, + { + "name": "导入", + "value": "user:Import", + "api": "/api/system/user/import/", + "method": 1 + }, + { + "name": "获取导入模板", + "value": "user:ImportTemplate", + "api": "/api/system/user/import/", + "method": 0 + }, + { + "name": "批量更新模板", + "value": "user:BatchUpdateTemplate", + "api": "/api/system/user/update_template/", + "method": 0 + }, + { + "name": "重设密码", + "value": "user:ResetPassword", + "api": "/api/system/user/{id}/reset_password/", + "method": 2 + }, + { + "name": "重置密码", + "value": "user:ResetDefaultPassword", + "api": "/api/system/user/{id}/reset_to_default_password/", + "method": 2 + } + ], + "menu_field": [ + { + "field_name": "avatar", + "title": "头像", + "model": "Users" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "Users" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "Users" + }, + { + "field_name": "dept", + "title": "所属部门", + "model": "Users" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "Users" + }, + { + "field_name": "description", + "title": "描述", + "model": "Users" + }, + { + "field_name": "email", + "title": "邮箱", + "model": "Users" + }, + { + "field_name": "gender", + "title": "性别", + "model": "Users" + }, + { + "field_name": "id", + "title": "Id", + "model": "Users" + }, + { + "field_name": "mobile", + "title": "电话", + "model": "Users" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "Users" + }, + { + "field_name": "name", + "title": "姓名", + "model": "Users" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "Users" + }, + { + "field_name": "username", + "title": "用户账号", + "model": "Users" + }, + { + "field_name": "user_type", + "title": "用户类型", + "model": "Users" + } + ] + }, + { + "name": "菜单管理", + "icon": "iconfont icon-caidan", + "sort": 2, + "is_link": false, + "is_catalog": false, + "web_path": "/menu", + "component": "system/menu/index", + "component_name": "menu", + "status": true, + "cache": false, + "visible": true, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "menu:Search", + "api": "/api/system/menu/", + "method": 0 + }, + { + "name": "单例", + "value": "menu:Retrieve", + "api": "/api/system/menu/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "menu:Create", + "api": "/api/system/menu/", + "method": 1 + }, + { + "name": "编辑", + "value": "menu:Update", + "api": "/api/system/menu/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "menu:Delete", + "api": "/api/system/menu/{id}/", + "method": 3 + }, + { + "name": "查询所有", + "value": "menu:SearchAll", + "api": "/api/system/menu/get_all_menu/", + "method": 0 + }, + { + "name": "路由", + "value": "menu:router", + "api": "/api/system/menu/web_router/", + "method": 0 + }, + { + "name": "查询按钮", + "value": "menu:SearchButton", + "api": "/api/system/menu_button/", + "method": 0 + }, + { + "name": "新增按钮", + "value": "menu:CreateButton", + "api": "/api/system/menu_button/", + "method": 1 + }, + { + "name": "编辑按钮", + "value": "menu:UpdateButton", + "api": "/api/system/menu_button/{id}/", + "method": 2 + }, + { + "name": "删除按钮", + "value": "menu:DeleteButton", + "api": "/api/system/menu_button/{id}/", + "method": 3 + }, + { + "name": "上移", + "value": "menu:MoveUp", + "api": "/api/system/menu/mode_up/", + "method": 1 + }, + { + "name": "下移", + "value": "menu:MoveDown", + "api": "/api/system/menu/mode_down/", + "method": 1 + }, + { + "name": "查询列权限", + "value": "column:Search", + "api": "/api/system/column/", + "method": 0 + }, + { + "name": "新增列权限", + "value": "column:Create", + "api": "/api/system/column/", + "method": 1 + }, + { + "name": "编辑列权限", + "value": "column:Update", + "api": "/api/system/column/{id}/", + "method": 2 + }, + { + "name": "删除列权限", + "value": "column:Delete", + "api": "/api/system/column/{id}/", + "method": 3 + }, + { + "name": "自动匹配列权限", + "value": "column:Match", + "api": "/api/system/column/auto_match_fields/", + "method": 1 + } + ], + "menu_field": [] + }, + { + "name": "部门管理", + "icon": "ele-OfficeBuilding", + "sort": 3, + "is_link": false, + "is_catalog": false, + "web_path": "/dept", + "component": "system/dept/index", + "component_name": "dept", + "status": true, + "cache": false, + "visible": true, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "dept:Search", + "api": "/api/system/dept/", + "method": 0 + }, + { + "name": "详情", + "value": "dept:Retrieve", + "api": "/api/system/dept/{id}/", + "method": 0 + }, + { + "name": "获取所有部门", + "value": "dept:SearchAll", + "api": "/api/system/dept/all_dept/", + "method": 0 + }, + { + "name": "部门顶部信息", + "value": "dept:HeaderInfo", + "api": "/api/system/dept/dept_info/", + "method": 0 + }, + { + "name": "新增", + "value": "dept:Create", + "api": "/api/system/dept/", + "method": 1 + }, + { + "name": "上移", + "value": "dept:MoveUp", + "api": "/api/system/dept/mode_up/", + "method": 1 + }, + { + "name": "下移", + "value": "dept:MoveDown", + "api": "/api/system/dept/mode_down/", + "method": 1 + }, + { + "name": "编辑", + "value": "dept:Update", + "api": "/api/system/dept/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "dept:Delete", + "api": "/api/system/dept/{id}/", + "method": 3 + } + ], + "menu_field": [] + }, + { + "name": "角色管理", + "icon": "ele-ColdDrink", + "sort": 4, + "is_link": false, + "is_catalog": false, + "web_path": "/role", + "component": "system/role/index", + "component_name": "role", + "status": true, + "cache": false, + "visible": true, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "role:Search", + "api": "/api/system/role/", + "method": 0 + }, + { + "name": "单例", + "value": "role:Retrieve", + "api": "/api/system/role/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "role:Create", + "api": "/api/system/role/", + "method": 1 + }, + { + "name": "编辑", + "value": "role:Update", + "api": "/api/system/role/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "role:Delete", + "api": "/api/system/role/{id}/", + "method": 3 + }, + { + "name": "获取所有可授权数据范围的部门", + "value": "role:AllDataRangeDept", + "api": "/api/system/role_menu_button_permision/role_to_dept_all/", + "method": 0 + }, + { + "name": "获取所有可授权菜单", + "value": "role:AllCanMenu", + "api": "/api/system/role_menu_button_permision/get_role_menu/", + "method": 0 + }, + { + "name": "获取所有已授权用户", + "value": "role:AllAuthorizedUser", + "api": "/api/system/role/get_role_users/", + "method": 0 + }, + { + "name": "获取菜单所有可授权按钮", + "value": "role:AllMenuButton", + "api": "/api/system/role_menu_button_permision/get_role_menu_btn_field/", + "method": 0 + }, + { + "name": "授权菜单", + "value": "role:SetMenu", + "api": "/api/system/role_menu_button_permision/set_role_menu/", + "method": 2 + }, + { + "name": "授权菜单按钮", + "value": "role:SetMenuButton", + "api": "/api/system/role_menu_button_permision/set_role_menu_btn/", + "method": 2 + }, + { + "name": "授权数据范围", + "value": "role:SetDataRange", + "api": "/api/system/role_menu_button_permision/set_role_menu_btn_data_range/", + "method": 2 + }, + { + "name": "获取所有用户", + "value": "role:AllUser", + "api": "/api/system/user/", + "method": 0 + }, + { + "name": "授权用户予角色", + "value": "role:SetUserRole", + "api": "/api/system/role/{id}/set_role_users/", + "method": 2 + } + ], + "menu_field": [ + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "Role" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "Role" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "Role" + }, + { + "field_name": "description", + "title": "描述", + "model": "Role" + }, + { + "field_name": "id", + "title": "Id", + "model": "Role" + }, + { + "field_name": "key", + "title": "权限字符", + "model": "Role" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "Role" + }, + { + "field_name": "name", + "title": "角色名称", + "model": "Role" + }, + { + "field_name": "sort", + "title": "角色顺序", + "model": "Role" + }, + { + "field_name": "status", + "title": "角色状态", + "model": "Role" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "Role" + } + ] + }, + { + "name": "消息中心", + "icon": "iconfont icon-xiaoxizhongxin", + "sort": 7, + "is_link": false, + "is_catalog": false, + "web_path": "/messageCenter", + "component": "system/messageCenter/index", + "component_name": "messageCenter", + "status": true, + "cache": false, + "visible": true, + "parent": 1, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "messageCenter:Search", + "api": "/api/system/message_center/", + "method": 0 + }, + { + "name": "详情", + "value": "messageCenter:Retrieve", + "api": "/api/system/message_center/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "messageCenter:Create", + "api": "/api/system/message_center/", + "method": 1 + }, + { + "name": "编辑", + "value": "messageCenter:Update", + "api": "/api/system/message_center/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "messageCenter:Delete", + "api": "/api/system/menu/{id}/", + "method": 3 + } + ], + "menu_field": [ + { + "field_name": "content", + "title": "内容", + "model": "MessageCenter" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "MessageCenter" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "MessageCenter" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "MessageCenter" + }, + { + "field_name": "description", + "title": "描述", + "model": "MessageCenter" + }, + { + "field_name": "id", + "title": "Id", + "model": "MessageCenter" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "MessageCenter" + }, + { + "field_name": "target_type", + "title": "目标类型", + "model": "MessageCenter" + }, + { + "field_name": "title", + "title": "标题", + "model": "MessageCenter" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "MessageCenter" + } + ] + }, + { + "name": "接口白名单", + "icon": "ele-SetUp", + "sort": 8, + "is_link": false, + "is_catalog": false, + "web_path": "/apiWhiteList", + "component": "system/whiteList/index", + "component_name": "whiteList", + "status": true, + "cache": false, + "visible": true, + "parent": 1, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "api_white_list:Search", + "api": "/api/system/api_white_list/", + "method": 0 + }, + { + "name": "详情", + "value": "api_white_list:Retrieve", + "api": "/api/system/api_white_list/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "api_white_list:Create", + "api": "/api/system/api_white_list/", + "method": 1 + }, + { + "name": "编辑", + "value": "api_white_list:Update", + "api": "/api/system/api_white_list/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "api_white_list:Delete", + "api": "/api/system/api_white_list/{id}/", + "method": 3 + } + ], + "menu_field": [ + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "ApiWhiteList" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "ApiWhiteList" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "ApiWhiteList" + }, + { + "field_name": "description", + "title": "描述", + "model": "ApiWhiteList" + }, + { + "field_name": "enable_datasource", + "title": "激活数据权限", + "model": "ApiWhiteList" + }, + { + "field_name": "id", + "title": "Id", + "model": "ApiWhiteList" + }, + { + "field_name": "method", + "title": "接口请求方法", + "model": "ApiWhiteList" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "ApiWhiteList" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "ApiWhiteList" + }, + { + "field_name": "url", + "title": "url", + "model": "ApiWhiteList" + } + ] + }, + { + "name": "下载中心", + "icon": "ele-Download", + "sort": 9, + "is_link": false, + "is_catalog": false, + "web_path": "/downloadCenter", + "component": "system/downloadCenter/index", + "component_name": "downloadCenter", + "status": true, + "cache": false, + "visible": true, + "parent": 277, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "downloadCenter:Search", + "api": "/api/system/download_center/" + } + ], + "menu_field": [] + } + ], + "menu_button": [], + "menu_field": [] + }, + { + "name": "常规配置", + "icon": "iconfont icon-configure", + "sort": 2, + "is_link": false, + "is_catalog": true, + "web_path": "/generalConfig", + "component": "", + "component_name": "", + "status": true, + "cache": false, + "visible": true, + "parent": null, + "children": [ + { + "name": "系统配置", + "icon": "iconfont icon-system", + "sort": 0, + "is_link": false, + "is_catalog": false, + "web_path": "/config", + "component": "system/config/index", + "component_name": "config", + "status": true, + "cache": false, + "visible": true, + "parent": 10, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "system_config:Search", + "api": "/api/system/system_config/", + "method": 0 + }, + { + "name": "详情", + "value": "system_config:Retrieve", + "api": "/api/system/system_config/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "system_config:Create", + "api": "/api/system/system_config/", + "method": 1 + }, + { + "name": "编辑", + "value": "system_config:Update", + "api": "/api/system/system_config/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "system_config:Delete", + "api": "/api/system/system_config/{id}/", + "method": 3 + } + ], + "menu_field": [] + }, + { + "name": "字典管理", + "icon": "iconfont icon-dict", + "sort": 1, + "is_link": false, + "is_catalog": false, + "web_path": "/dictionary", + "component": "system/dictionary/index", + "component_name": "dictionary", + "status": true, + "cache": false, + "visible": true, + "parent": 10, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "dictionary:Search", + "api": "/api/system/dictionary/", + "method": 0 + }, + { + "name": "详情", + "value": "dictionary:Retrieve", + "api": "/api/system/dictionary/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "dictionary:Create", + "api": "/api/system/dictionary/", + "method": 1 + }, + { + "name": "编辑", + "value": "dictionary:Update", + "api": "/api/system/dictionary/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "dictionary:Delete", + "api": "/api/system/dictionary/{id}/", + "method": 3 + } + ], + "menu_field": [ + { + "field_name": "color", + "title": "颜色", + "model": "Dictionary" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "Dictionary" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "Dictionary" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "Dictionary" + }, + { + "field_name": "description", + "title": "描述", + "model": "Dictionary" + }, + { + "field_name": "id", + "title": "Id", + "model": "Dictionary" + }, + { + "field_name": "is_value", + "title": "是否为value值", + "model": "Dictionary" + }, + { + "field_name": "label", + "title": "字典名称", + "model": "Dictionary" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "Dictionary" + }, + { + "field_name": "parent", + "title": "父级", + "model": "Dictionary" + }, + { + "field_name": "remark", + "title": "备注", + "model": "Dictionary" + }, + { + "field_name": "sort", + "title": "显示排序", + "model": "Dictionary" + }, + { + "field_name": "status", + "title": "状态", + "model": "Dictionary" + }, + { + "field_name": "type", + "title": "数据值类型", + "model": "Dictionary" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "Dictionary" + }, + { + "field_name": "value", + "title": "字典编号", + "model": "Dictionary" + } + ] + }, + { + "name": "地区管理", + "icon": "iconfont icon-Area", + "sort": 2, + "is_link": false, + "is_catalog": false, + "web_path": "/areas", + "component": "system/areas/index", + "component_name": "areas", + "status": true, + "cache": false, + "visible": true, + "parent": 10, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "area:Search", + "api": "/api/system/area/", + "method": 0 + }, + { + "name": "详情", + "value": "area:Retrieve", + "api": "/api/system/area/{id}/", + "method": 0 + }, + { + "name": "新增", + "value": "area:Create", + "api": "/api/system/area/", + "method": 1 + }, + { + "name": "编辑", + "value": "area:Update", + "api": "/api/system/area/{id}/", + "method": 2 + }, + { + "name": "删除", + "value": "area:Delete", + "api": "/api/system/area/{id}/", + "method": 3 + } + ], + "menu_field": [ + { + "field_name": "code", + "title": "地区编码", + "model": "Area" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "Area" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "Area" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "Area" + }, + { + "field_name": "description", + "title": "描述", + "model": "Area" + }, + { + "field_name": "enable", + "title": "是否启用", + "model": "Area" + }, + { + "field_name": "id", + "title": "Id", + "model": "Area" + }, + { + "field_name": "initials", + "title": "首字母", + "model": "Area" + }, + { + "field_name": "level", + "title": "地区层级(1省份 2城市 3区县 4乡级)", + "model": "Area" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "Area" + }, + { + "field_name": "name", + "title": "名称", + "model": "Area" + }, + { + "field_name": "pcode", + "title": "父地区编码", + "model": "Area" + }, + { + "field_name": "pinyin", + "title": "拼音", + "model": "Area" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "Area" + } + ] + }, + { + "name": "附件管理", + "icon": "iconfont icon-file", + "sort": 3, + "is_link": false, + "is_catalog": false, + "web_path": "/file", + "component": "system/fileList/index", + "component_name": "file", + "status": true, + "cache": false, + "visible": true, + "parent": 10, + "children": [], + "menu_button": [ + { + "name": "详情", + "value": "file:Retrieve", + "api": "/api/system/file/{id}/", + "method": 0 + }, + { + "name": "查询", + "value": "file:Search", + "api": "/api/system/file/", + "method": 0 + }, + { + "name": "编辑", + "value": "file:Update", + "api": "/api/system/file/{id}/", + "method": 1 + }, + { + "name": "删除", + "value": "file:Delete", + "api": "/api/system/file/{id}/", + "method": 3 + } + ], + "menu_field": [ + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "FileList" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "FileList" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "FileList" + }, + { + "field_name": "description", + "title": "描述", + "model": "FileList" + }, + { + "field_name": "engine", + "title": "引擎", + "model": "FileList" + }, + { + "field_name": "file_url", + "title": "文件地址", + "model": "FileList" + }, + { + "field_name": "id", + "title": "Id", + "model": "FileList" + }, + { + "field_name": "md5sum", + "title": "文件md5", + "model": "FileList" + }, + { + "field_name": "mime_type", + "title": "Mime类型", + "model": "FileList" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "FileList" + }, + { + "field_name": "name", + "title": "名称", + "model": "FileList" + }, + { + "field_name": "size", + "title": "文件大小", + "model": "FileList" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "FileList" + }, + { + "field_name": "url", + "title": "url", + "model": "FileList" + } + ] + } + ], + "menu_button": [], + "menu_field": [] + }, + { + "name": "日志管理", + "icon": "iconfont icon-rizhi", + "sort": 3, + "is_link": false, + "is_catalog": true, + "web_path": "/log", + "component": "", + "component_name": "", + "status": true, + "cache": false, + "visible": true, + "parent": null, + "children": [ + { + "name": "登录日志", + "icon": "iconfont icon-guanlidenglurizhi", + "sort": 1, + "is_link": false, + "is_catalog": false, + "web_path": "/loginLog", + "component": "system/log/loginLog/index", + "component_name": "loginLog", + "status": true, + "cache": false, + "visible": true, + "parent": 15, + "children": [], + "menu_button": [ + { + "name": "查询", + "value": "login_log:Search", + "api": "/api/system/login_log/", + "method": 0 + }, + { + "name": "详情", + "value": "login_log:Retrieve", + "api": "/api/system/login_log/{id}/", + "method": 0 + } + ], + "menu_field": [ + { + "field_name": "agent", + "title": "agent信息", + "model": "LoginLog" + }, + { + "field_name": "area_code", + "title": "区域代码", + "model": "LoginLog" + }, + { + "field_name": "browser", + "title": "浏览器名", + "model": "LoginLog" + }, + { + "field_name": "city", + "title": "城市", + "model": "LoginLog" + }, + { + "field_name": "continent", + "title": "州", + "model": "LoginLog" + }, + { + "field_name": "country", + "title": "国家", + "model": "LoginLog" + }, + { + "field_name": "country_code", + "title": "简称", + "model": "LoginLog" + }, + { + "field_name": "country_english", + "title": "英文全称", + "model": "LoginLog" + }, + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "LoginLog" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "LoginLog" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "LoginLog" + }, + { + "field_name": "description", + "title": "描述", + "model": "LoginLog" + }, + { + "field_name": "district", + "title": "县区", + "model": "LoginLog" + }, + { + "field_name": "id", + "title": "Id", + "model": "LoginLog" + }, + { + "field_name": "ip", + "title": "登录ip", + "model": "LoginLog" + }, + { + "field_name": "isp", + "title": "运营商", + "model": "LoginLog" + }, + { + "field_name": "latitude", + "title": "纬度", + "model": "LoginLog" + }, + { + "field_name": "login_type", + "title": "登录类型", + "model": "LoginLog" + }, + { + "field_name": "longitude", + "title": "经度", + "model": "LoginLog" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "LoginLog" + }, + { + "field_name": "os", + "title": "操作系统", + "model": "LoginLog" + }, + { + "field_name": "province", + "title": "省份", + "model": "LoginLog" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "LoginLog" + }, + { + "field_name": "username", + "title": "登录用户名", + "model": "LoginLog" + } + ] + }, + { + "name": "操作日志", + "icon": "iconfont icon-caozuorizhi", + "sort": 2, + "is_link": false, + "is_catalog": false, + "web_path": "/operationLog", + "component": "system/log/operationLog/index", + "component_name": "operationLog", + "status": true, + "cache": false, + "visible": true, + "parent": 15, + "children": [], + "menu_button": [ + { + "name": "详情", + "value": "operation_log:Retrieve", + "api": "/api/system/operation_log/{id}/", + "method": 0 + }, + { + "name": "查询", + "value": "operation_log:Search", + "api": "/api/system/operation_log/", + "method": 0 + } + ], + "menu_field": [ + { + "field_name": "create_datetime", + "title": "创建时间", + "model": "OperationLog" + }, + { + "field_name": "creator", + "title": "创建人", + "model": "OperationLog" + }, + { + "field_name": "dept_belong_id", + "title": "数据归属部门", + "model": "OperationLog" + }, + { + "field_name": "description", + "title": "描述", + "model": "OperationLog" + }, + { + "field_name": "id", + "title": "Id", + "model": "OperationLog" + }, + { + "field_name": "json_result", + "title": "返回信息", + "model": "OperationLog" + }, + { + "field_name": "modifier", + "title": "修改人", + "model": "OperationLog" + }, + { + "field_name": "request_body", + "title": "请求参数", + "model": "OperationLog" + }, + { + "field_name": "request_browser", + "title": "请求浏览器", + "model": "OperationLog" + }, + { + "field_name": "request_ip", + "title": "请求ip地址", + "model": "OperationLog" + }, + { + "field_name": "request_method", + "title": "请求方式", + "model": "OperationLog" + }, + { + "field_name": "request_modular", + "title": "请求模块", + "model": "OperationLog" + }, + { + "field_name": "request_msg", + "title": "操作说明", + "model": "OperationLog" + }, + { + "field_name": "request_os", + "title": "操作系统", + "model": "OperationLog" + }, + { + "field_name": "request_path", + "title": "请求地址", + "model": "OperationLog" + }, + { + "field_name": "response_code", + "title": "响应状态码", + "model": "OperationLog" + }, + { + "field_name": "status", + "title": "响应状态", + "model": "OperationLog" + }, + { + "field_name": "update_datetime", + "title": "修改时间", + "model": "OperationLog" + } + ] + } + ], + "menu_button": [], + "menu_field": [] + } +] \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_role.json b/backend/dvadmin/system/fixtures/init_role.json new file mode 100644 index 0000000..f1678e5 --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_role.json @@ -0,0 +1,16 @@ +[ + { + "name": "管理员", + "key": "admin", + "sort": 1, + "status": true, + "remark": null + }, + { + "name": "用户", + "key": "public", + "sort": 2, + "status": true, + "remark": null + } +] diff --git a/backend/dvadmin/system/fixtures/init_rolemenubuttonpermission.json b/backend/dvadmin/system/fixtures/init_rolemenubuttonpermission.json new file mode 100644 index 0000000..8057cd8 --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_rolemenubuttonpermission.json @@ -0,0 +1,7 @@ +[ + { + "role__key": "admin", + "menu_button__value": "menu:Search", + "data_range": 0 + } +] diff --git a/backend/dvadmin/system/fixtures/init_rolemenupermission.json b/backend/dvadmin/system/fixtures/init_rolemenupermission.json new file mode 100644 index 0000000..7a6244e --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_rolemenupermission.json @@ -0,0 +1,12 @@ +[ + { + "role__key": "admin", + "menu__web_path": "/system", + "menu__component_name": "" + }, + { + "role__key": "admin", + "menu__web_path": "/menu", + "menu__component_name": "menu" + } +] diff --git a/backend/dvadmin/system/fixtures/init_systemconfig.json b/backend/dvadmin/system/fixtures/init_systemconfig.json new file mode 100644 index 0000000..61e100e --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_systemconfig.json @@ -0,0 +1,486 @@ +[ + { + "parent": null, + "title": "基础配置", + "key": "base", + "value": null, + "sort": 0, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": null, + "placeholder": null, + "setting": null, + "children": [ + { + "parent": 10, + "title": "网页标题", + "key": "web_title", + "value": "PIS", + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [], + "placeholder": "请输入网站标题", + "setting": null, + "children": [] + }, + { + "parent": 10, + "title": "网站小图标", + "key": "web_favicon", + "value": "", + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [], + "placeholder": "请输入网站小图标", + "setting": null, + "children": [] + }, + { + "parent": 10, + "title": "开启验证码", + "key": "captcha_state", + "value": true, + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 9, + "rule": [ + { + "message": "必填项不能为空", + "required": true + } + ], + "placeholder": "请选择", + "setting": null, + "children": [] + }, + { + "parent": 10, + "title": "创建用户默认密码", + "key": "default_password", + "value": "admin123456", + "sort": 2, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "message": "必填项不能为空", + "required": true + } + ], + "placeholder": "请输入默认密码", + "setting": null, + "children": [] + } + ] + }, + { + "parent": null, + "title": "登录页配置", + "key": "login", + "value": null, + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": null, + "placeholder": null, + "setting": null, + "children": [ + { + "parent": 1, + "title": "网站标题", + "key": "site_title", + "value": "PIS", + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [], + "placeholder": "请输入网站标题", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "网站名称", + "key": "site_name", + "value": "企业级后台管理系统", + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "message": "必填项不能为空", + "required": true + } + ], + "placeholder": "请输入网站名称", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "登录网站logo", + "key": "site_logo", + "value": null, + "sort": 2, + "status": true, + "data_options": null, + "form_item_type": 7, + "rule": [], + "placeholder": "请上传网站logo", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "登录页背景图", + "key": "login_background", + "value": null, + "sort": 3, + "status": true, + "data_options": null, + "form_item_type": 7, + "rule": [], + "placeholder": "请上传登录背景页", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "版权信息", + "key": "copyright", + "value": "2021-2024 django-vue-admin.com 版权所有", + "sort": 4, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "message": "必填项不能为空", + "required": true + } + ], + "placeholder": "请输入版权信息", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "备案信息", + "key": "keep_record", + "value": "晋ICP备18005113号-3", + "sort": 5, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "message": "必填项不能为空", + "required": true + } + ], + "placeholder": "请输入备案信息", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "帮助链接", + "key": "help_url", + "value": "https://django-vue-admin.com", + "sort": 6, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": "", + "placeholder": "请输入帮助信息", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "隐私链接", + "key": "privacy_url", + "value": "/api/system/clause/privacy.html", + "sort": 7, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [], + "placeholder": "请填写隐私链接", + "setting": null, + "children": [] + }, + { + "parent": 1, + "title": "条款链接", + "key": "clause_url", + "value": "/api/system/clause/terms_service.html", + "sort": 8, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": [], + "placeholder": "请输入条款链接", + "setting": null, + "children": [] + } + ] + }, + { + "title": "文件存储配置", + "key": "file_storage", + "value": null, + "sort": 0, + "status": true, + "data_options": null, + "form_item_type": 0, + "rule": null, + "placeholder": null, + "setting": null, + "children": [ + { + "title": "存储引擎", + "key": "file_engine", + "value": "local", + "sort": 1, + "status": true, + "data_options": null, + "form_item_type": 4, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请选择存储引擎", + "setting": "file_engine", + "children": [] + }, + { + "title": "文件是否备份", + "key": "file_backup", + "value": false, + "sort": 2, + "status": true, + "data_options": null, + "form_item_type": 9, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "启用云存储时,文件是否备份到本地", + "setting": null, + "children": [] + }, + { + "title": "阿里云-AccessKey", + "key": "aliyun_access_key", + "value": null, + "sort": 3, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入AccessKey", + "setting": null, + "children": [] + }, + { + "title": "阿里云-Secret", + "key": "aliyun_access_secret", + "value": null, + "sort": 4, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入Secret", + "setting": null, + "children": [] + }, + { + "title": "阿里云-Endpoint", + "key": "aliyun_endpoint", + "value": null, + "sort": 5, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入Endpoint", + "setting": null, + "children": [] + }, + { + "title": "阿里云-上传路径", + "key": "aliyun_path", + "value": "/media/", + "sort": 5, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入上传路径", + "setting": null, + "children": [] + }, + { + "title": "阿里云-Bucket", + "key": "aliyun_bucket", + "value": null, + "sort": 7, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入Bucket", + "setting": null, + "children": [] + },{ + "title": "阿里云-cdn地址", + "key": "aliyun_cdn_url", + "value": null, + "sort": 7, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入cdn地址", + "setting": null, + "children": [] + }, + { + "title": "腾讯云-SecretId", + "key": "tencent_secret_id", + "value": null, + "sort": 8, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入SecretId", + "setting": null, + "children": [] + }, + { + "title": "腾讯云-SecretKey", + "key": "tencent_secret_key", + "value": null, + "sort": 9, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入SecretKey", + "setting": null, + "children": [] + }, + { + "title": "腾讯云-Region", + "key": "tencent_region", + "value": null, + "sort": 10, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入Region", + "setting": null, + "children": [] + }, + { + "title": "腾讯云-Bucket", + "key": "tencent_bucket", + "value": null, + "sort": 11, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入Bucket", + "setting": null, + "children": [] + }, + { + "title": "腾讯云-上传路径", + "key": "tencent_path", + "value": "/media/", + "sort": 12, + "status": false, + "data_options": null, + "form_item_type": 0, + "rule": [ + { + "required": false, + "message": "必填项不能为空" + } + ], + "placeholder": "请输入上传路径", + "setting": null, + "children": [] + } + ] + } +] \ No newline at end of file diff --git a/backend/dvadmin/system/fixtures/init_users.json b/backend/dvadmin/system/fixtures/init_users.json new file mode 100644 index 0000000..eb3f52e --- /dev/null +++ b/backend/dvadmin/system/fixtures/init_users.json @@ -0,0 +1,60 @@ +[ + { + "username": "superadmin", + "email": "dvadmin@django-vue-admin.com", + "mobile": "13333333333", + "avatar": null, + "name": "超级管理员", + "gender": 1, + "user_type": 0, + "role": [], + "role_key": [ + "admin" + ], + "dept_key": "dvadmin", + "first_name": "", + "last_name": "", + "is_staff": true, + "is_active": true, + "password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=", + "last_login": null, + "is_superuser": true + }, + { + "username": "admin", + "email": "dvadmin@django-vue-admin.com", + "mobile": "18888888888", + "avatar": "", + "name": "管理员", + "gender": 1, + "user_type": 0, + "role": [], + "dept_key": "dvadmin", + "first_name": "", + "last_name": "", + "is_staff": true, + "is_active": true, + "password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=", + "last_login": null, + "is_superuser": false + }, + { + "username": "test", + "email": "dvadmin@django-vue-admin.com", + "mobile": "18888888888", + "avatar": "", + "name": "测试人员", + "gender": 1, + "user_type": 0, + "role": [], + "role_key": ["public"], + "dept_key": "technology", + "first_name": "", + "last_name": "", + "is_staff": true, + "is_active": true, + "password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=", + "last_login": null, + "is_superuser": false + } +] diff --git a/backend/dvadmin/system/fixtures/initialize.py b/backend/dvadmin/system/fixtures/initialize.py new file mode 100644 index 0000000..f409643 --- /dev/null +++ b/backend/dvadmin/system/fixtures/initialize.py @@ -0,0 +1,87 @@ +# 初始化 +import os + +import django + + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings") +django.setup() + +from dvadmin.utils.core_initialize import CoreInitialize +from dvadmin.system.fixtures.initSerializer import ( + UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, + MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, + SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer +) + + +class Initialize(CoreInitialize): + + def init_dept(self): + """ + 初始化部门信息 + """ + self.init_base(DeptInitSerializer, unique_fields=['name', 'parent','key']) + + def init_role(self): + """ + 初始化角色信息 + """ + self.init_base(RoleInitSerializer, unique_fields=['key']) + + def init_users(self): + """ + 初始化用户信息 + """ + self.init_base(UsersInitSerializer, unique_fields=['username']) + + def init_menu(self): + """ + 初始化菜单信息 + """ + self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name']) + + def init_role_menu(self): + """ + 初始化角色菜单信息 + """ + self.init_base(RoleMenuInitSerializer, unique_fields=['role__key', 'menu__web_path', 'menu__component_name']) + + def init_role_menu_button(self): + """ + 初始化角色菜单按钮信息 + """ + self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role__key', 'menu_button__value']) + + def init_api_white_list(self): + """ + 初始API白名单 + """ + self.init_base(ApiWhiteListInitSerializer, unique_fields=['url', 'method', ]) + + def init_dictionary(self): + """ + 初始化字典表 + """ + self.init_base(DictionaryInitSerializer, unique_fields=['value', 'parent', ]) + + def init_system_config(self): + """ + 初始化系统配置表 + """ + self.init_base(SystemConfigInitSerializer, unique_fields=['key', 'parent', ]) + + def run(self): + self.init_dept() + self.init_role() + self.init_users() + self.init_menu() + self.init_role_menu() + self.init_role_menu_button() + self.init_api_white_list() + self.init_dictionary() + self.init_system_config() + + +if __name__ == "__main__": + Initialize(app='dvadmin.system').run() diff --git a/backend/dvadmin/system/management/__init__.py b/backend/dvadmin/system/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/management/commands/__init__.py b/backend/dvadmin/system/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/management/commands/check_dept_users.py b/backend/dvadmin/system/management/commands/check_dept_users.py new file mode 100644 index 0000000..9294152 --- /dev/null +++ b/backend/dvadmin/system/management/commands/check_dept_users.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +""" +排查 Dept 层级数据及 Users 关联 +用法: python manage.py check_dept_users +""" +from django.core.management.base import BaseCommand +from django.db import connection +class Command(BaseCommand): + help = "排查 Dept 表层级数据、Users 重复及部门 parent 关联" + + def handle(self, *args, **options): + self.stdout.write("=" * 60) + self.stdout.write("1. 检查 Users 表是否有重复 id") + self.stdout.write("=" * 60) + self._check_duplicate_users() + + self.stdout.write("\n" + "=" * 60) + self.stdout.write("2. 检查 Dept 表是否有重复 id") + self.stdout.write("=" * 60) + self._check_duplicate_depts() + + self.stdout.write("\n" + "=" * 60) + self.stdout.write("3. 检查 Dept parent 关联(parent_id 指向不存在的部门)") + self.stdout.write("=" * 60) + self._check_dept_parent_orphans() + + self.stdout.write("\n" + "=" * 60) + self.stdout.write("4. 检查 Dept 层级循环(parent 链成环)") + self.stdout.write("=" * 60) + self._check_dept_cycles() + + self.stdout.write("\n" + "=" * 60) + self.stdout.write("5. 用户及其部门 parent 链(前 20 条)") + self.stdout.write("=" * 60) + self._show_users_dept_chain() + + self.stdout.write("\n" + "=" * 60) + self.stdout.write("6. Dept 全表 id/parent_id/name(便于人工核对)") + self.stdout.write("=" * 60) + self._show_all_depts() + + def _run_sql(self, sql, params=None): + with connection.cursor() as c: + c.execute(sql, params or []) + columns = [col[0] for col in c.description] + return [dict(zip(columns, row)) for row in c.fetchall()] + + def _get_table(self, name): + from django.conf import settings + prefix = getattr(settings, "TABLE_PREFIX", "") + return f"{prefix}{name}" + + def _check_duplicate_users(self): + t = self._get_table("system_users") + # 重复 id + rows = self._run_sql( + f"SELECT id, COUNT(*) as cnt FROM {t} GROUP BY id HAVING COUNT(*) > 1" + ) + if rows: + self.stdout.write(self.style.ERROR(f" [!!] 发现重复 Users.id: {rows}")) + for r in rows: + detail = self._run_sql(f"SELECT id, username, name, dept_id FROM {t} WHERE id = %s", [r["id"]]) + self.stdout.write(f" 详情: {detail}") + else: + self.stdout.write(self.style.SUCCESS(" [OK] Users 表无重复 id")) + # 重复 username(JWT 若按 username 查会触发 MultipleObjectsReturned) + dup_user = self._run_sql( + f"SELECT username, COUNT(*) as cnt FROM {t} GROUP BY username HAVING COUNT(*) > 1" + ) + if dup_user: + self.stdout.write(self.style.ERROR(f" [!!] 发现重复 username: {dup_user}")) + else: + self.stdout.write(self.style.SUCCESS(" [OK] Users 表无重复 username")) + + def _check_duplicate_depts(self): + t = self._get_table("system_dept") + rows = self._run_sql( + f"SELECT id, COUNT(*) as cnt FROM {t} GROUP BY id HAVING COUNT(*) > 1" + ) + if not rows: + self.stdout.write(self.style.SUCCESS(" [OK] Dept 表无重复 id")) + return + self.stdout.write(self.style.ERROR(f" [!!] 发现重复 Dept.id: {rows}")) + + def _check_dept_parent_orphans(self): + t = self._get_table("system_dept") + # parent_id 非空且指向不存在的 id + rows = self._run_sql(f""" + SELECT d.id, d.name, d.parent_id + FROM {t} d + LEFT JOIN {t} p ON d.parent_id = p.id + WHERE d.parent_id IS NOT NULL AND p.id IS NULL + """) + if not rows: + self.stdout.write(self.style.SUCCESS(" [OK] 无 parent 指向不存在的部门")) + return + self.stdout.write(self.style.ERROR(f" [!!] 以下部门 parent_id 指向不存在的 id:")) + for r in rows: + self.stdout.write(f" id={r['id']} name={r['name']} parent_id={r['parent_id']}") + + def _check_dept_cycles(self): + t = self._get_table("system_dept") + rows = self._run_sql(f"SELECT id, parent_id, name FROM {t} ORDER BY id") + dept_map = {r["id"]: r for r in rows} + cycles = [] + for d in rows: + seen = set() + cur = d + while cur and cur["parent_id"]: + if cur["id"] in seen: + cycles.append((d["id"], d["name"], list(seen))) + break + seen.add(cur["id"]) + cur = dept_map.get(cur["parent_id"]) + if not cycles: + self.stdout.write(self.style.SUCCESS(" [OK] 未发现 parent 链成环")) + return + self.stdout.write(self.style.ERROR(f" [!!] 发现循环: {cycles}")) + + def _show_users_dept_chain(self): + """用 raw SQL 避免 ORM 触发 MultipleObjectsReturned""" + ut = self._get_table("system_users") + dt = self._get_table("system_dept") + users = self._run_sql( + f"SELECT id, username, name, dept_id FROM {ut} ORDER BY id LIMIT 20" + ) + depts = {r["id"]: r for r in self._run_sql(f"SELECT id, parent_id, name FROM {dt}")} + for u in users: + dept_id = u.get("dept_id") + if not dept_id: + self.stdout.write(f" user id={u['id']} username={u['username']} dept=None") + continue + chain = [] + cur_id = dept_id + visited = set() + while cur_id: + if cur_id in visited: + chain.append(f"[循环!id={cur_id}]") + break + d = depts.get(cur_id) + if not d: + chain.append(f"[不存在id={cur_id}]") + break + visited.add(cur_id) + chain.append(f"{d['name']}(id={d['id']})") + cur_id = d.get("parent_id") + self.stdout.write(f" user id={u['id']} username={u['username']} dept_chain: {' -> '.join(chain)}") + + def _show_all_depts(self): + t = self._get_table("system_dept") + rows = self._run_sql(f"SELECT id, parent_id, name FROM {t} ORDER BY id") + self.stdout.write(" id | parent_id | name") + self.stdout.write(" " + "-" * 50) + for r in rows: + self.stdout.write(f" {r['id']} | {r['parent_id']} | {r['name']}") diff --git a/backend/dvadmin/system/management/commands/generate_init_json.py b/backend/dvadmin/system/management/commands/generate_init_json.py new file mode 100644 index 0000000..e0de3e3 --- /dev/null +++ b/backend/dvadmin/system/management/commands/generate_init_json.py @@ -0,0 +1,103 @@ +import json +import logging +import os + +import django +from django.db.models import QuerySet + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +django.setup() +from django.core.management.base import BaseCommand + +from application.settings import BASE_DIR +from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig, RoleMenuButtonPermission, RoleMenuPermission +from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \ + MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \ + RoleMenuInitSerializer, RoleMenuButtonInitSerializer + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + 生产初始化菜单: python3 manage.py generate_init_json 生成初始化的model名 + 例如: + 全部生成:python3 manage.py generate_init_json + 只生成某个model的: python3 manage.py generate_init_json users + """ + + def serializer_data(self, serializer, query_set: QuerySet): + serializer = serializer(query_set, many=True) + data = json.loads(json.dumps(serializer.data, ensure_ascii=False)) + with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w',encoding='utf-8') as f: + json.dump(data, f, indent=4, ensure_ascii=False) + return + + def add_arguments(self, parser): + parser.add_argument("generate_name", nargs="*", type=str, help="初始化生成的表名") + + def generate_users(self): + self.serializer_data(UsersInitSerializer, Users.objects.all()) + + def generate_role(self): + self.serializer_data(RoleInitSerializer, Role.objects.all()) + + def generate_dept(self): + self.serializer_data(DeptInitSerializer, Dept.objects.filter(parent_id__isnull=True)) + + def generate_menu(self): + self.serializer_data(MenuInitSerializer, Menu.objects.filter(parent_id__isnull=True)) + + def generate_api_white_list(self): + self.serializer_data(ApiWhiteListInitSerializer, ApiWhiteList.objects.all()) + + def generate_dictionary(self): + self.serializer_data(DictionaryInitSerializer, Dictionary.objects.filter(parent_id__isnull=True)) + + def generate_system_config(self): + self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True)) + + def generate_role_menu(self): + self.serializer_data(RoleMenuInitSerializer, RoleMenuPermission.objects.all()) + + def generate_role_menu_button(self): + self.serializer_data(RoleMenuButtonInitSerializer, RoleMenuButtonPermission.objects.all()) + + def handle(self, *args, **options): + generate_name = options.get('generate_name') + generate_name_dict = { + "users": self.generate_users, + "role": self.generate_role, + "dept": self.generate_dept, + "menu": self.generate_menu, + "api_white_list": self.generate_api_white_list, + "dictionary": self.generate_dictionary, + "system_config": self.generate_system_config, + "role_menu": self.generate_role_menu, + "role_menu_button": self.generate_role_menu_button, + } + if not generate_name: + for ele in generate_name_dict.keys(): + generate_name_dict[ele]() + return + + for generate_name in generate_name: + if generate_name not in generate_name_dict: + print(f"该初始化方法尚未配置\n{generate_name_dict}") + raise Exception(f"该初始化方法尚未配置,已配置项:{list(generate_name_dict.keys())}") + generate_name_dict[generate_name]() + return + + +if __name__ == '__main__': + # with open(os.path.join(BASE_DIR, 'temp_init_menu.json')) as f: + # for menu_data in json.load(f): + # menu_data['creator'] = 1 + # menu_data['modifier'] = 1 + # menu_data['dept_belong_id'] = 1 + # request.user = Users.objects.order_by('create_datetime').first() + # serializer = MenuInitSerializer(data=menu_data, request=request) + # serializer.is_valid(raise_exception=True) + # serializer.save() + a = Users.objects.filter() + print(type(Users.objects.filter())) diff --git a/backend/dvadmin/system/management/commands/init.py b/backend/dvadmin/system/management/commands/init.py new file mode 100644 index 0000000..58154a8 --- /dev/null +++ b/backend/dvadmin/system/management/commands/init.py @@ -0,0 +1,56 @@ +import logging + +from django.core.management.base import BaseCommand + +from application import settings + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + """ + 项目初始化命令: python manage.py init + """ + + def add_arguments(self, parser): + parser.add_argument( + "init_name", + nargs="*", + type=str, + ) + parser.add_argument("-y", nargs="*") + parser.add_argument("-Y", nargs="*") + parser.add_argument("-n", nargs="*") + parser.add_argument("-N", nargs="*") + parser.add_argument("-app", nargs="*") + parser.add_argument("-A", nargs="*") + + def handle(self, *args, **options): + reset = False + if isinstance(options.get("y"), list) or isinstance(options.get("Y"), list): + reset = True + if isinstance(options.get("n"), list) or isinstance(options.get("N"), list): + reset = False + assign_apps = options.get("app") or options.get("A") or [] + for app in settings.INSTALLED_APPS: + if assign_apps and app not in assign_apps: + continue + try: + exec( + f""" +from {app}.fixtures.initialize import Initialize +Initialize(reset={reset},app="{app}").run() + """ + ) + except ModuleNotFoundError: + # 兼容之前版本初始化 + try: + exec( + f""" +from {app}.initialize import main +main(reset={reset}) + """ + ) + except ModuleNotFoundError: + pass + print("初始化数据完成!") diff --git a/backend/dvadmin/system/management/commands/init_area.py b/backend/dvadmin/system/management/commands/init_area.py new file mode 100644 index 0000000..24136d9 --- /dev/null +++ b/backend/dvadmin/system/management/commands/init_area.py @@ -0,0 +1,83 @@ +# 城市联动 +""" +到乡级 使用方法 +1. https://www.npmjs.com/package/china-division 下载数据,把对应的json放入对应目录 +2. 修改此文件中对应json名 +3. 右击执行此py文件进行初始化 +""" +import json +import os + +import django +import pypinyin +from django.core.management import BaseCommand +from django.db import connection + +from application import dispatch + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +django.setup() +from application.settings import BASE_DIR +from dvadmin.system.models import Area + +area_code_list = [] + + +def area_list(code_list, pcode=None, depth=1): + """ + 递归获取所有列表 + """ + for code_dict in code_list: + code = code_dict.get('code', None) + name = code_dict.get('name', None) + children = code_dict.get('children', None) + pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(name, style=pypinyin.NORMAL)]) + area_code_list.append( + { + "name": name, + "code": code, + "level": depth, + "pinyin": pinyin, + "initials": pinyin[0].upper() if pinyin else "#", + "pcode_id": pcode, + } + ) + if children: + area_list(code_list=children, pcode=code, depth=depth + 1) + + +def main(): + with open(os.path.join(BASE_DIR, 'dvadmin', 'system', 'util', 'pca-code.json'), 'r', encoding="utf-8") as load_f: + code_list = json.load(load_f) + area_list(code_list) + if Area.objects.count() == 0: + Area.objects.bulk_create([Area(**ele) for ele in area_code_list]) + else: + for ele in area_code_list: + code = ele.pop("code") + Area.objects.update_or_create(code=code, defaults=ele) + + +class Command(BaseCommand): + """ + 项目初始化命令: python manage.py init + """ + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + + print(f"正在准备初始化省份数据...") + + if dispatch.is_tenants_mode(): + from django_tenants.utils import get_tenant_model + from django_tenants.utils import tenant_context + for tenant in get_tenant_model().objects.exclude(schema_name='public'): + with tenant_context(tenant): + print(f"租户[{connection.tenant.schema_name}]初始化数据开始...") + main() + print(f"租户[{connection.tenant.schema_name}]初始化数据完成!") + else: + main() + print("省份数据初始化数据完成!") diff --git a/backend/dvadmin/system/migrations/0001_initial.py b/backend/dvadmin/system/migrations/0001_initial.py new file mode 100644 index 0000000..c7dc56e --- /dev/null +++ b/backend/dvadmin/system/migrations/0001_initial.py @@ -0,0 +1,574 @@ +# Generated by Django 4.2.14 on 2026-03-23 11:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import dvadmin.system.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Users', + fields=[ + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('username', models.CharField(db_index=True, help_text='用户账号', max_length=150, unique=True, verbose_name='用户账号')), + ('email', models.EmailField(blank=True, help_text='邮箱', max_length=255, null=True, verbose_name='邮箱')), + ('mobile', models.CharField(blank=True, help_text='电话', max_length=255, null=True, verbose_name='电话')), + ('avatar', models.CharField(blank=True, help_text='头像', max_length=255, null=True, verbose_name='头像')), + ('name', models.CharField(help_text='姓名', max_length=40, verbose_name='姓名')), + ('gender', models.IntegerField(blank=True, choices=[(0, '未知'), (1, '男'), (2, '女')], default=0, help_text='性别', null=True, verbose_name='性别')), + ('user_type', models.IntegerField(blank=True, choices=[(0, '后台用户'), (1, '前台用户')], default=0, help_text='用户类型', null=True, verbose_name='用户类型')), + ('login_error_count', models.IntegerField(default=0, help_text='登录错误次数', verbose_name='登录错误次数')), + ('pwd_change_count', models.IntegerField(blank=True, default=0, help_text='密码修改次数', verbose_name='密码修改次数')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '用户表', + 'verbose_name_plural': '用户表', + 'db_table': 'pis_system_users', + 'ordering': ('-create_datetime',), + }, + managers=[ + ('objects', dvadmin.system.models.CustomUserManager()), + ], + ), + migrations.CreateModel( + name='Dept', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(help_text='部门名称', max_length=64, verbose_name='部门名称')), + ('key', models.CharField(blank=True, help_text='关联字符', max_length=64, null=True, unique=True, verbose_name='关联字符')), + ('sort', models.IntegerField(default=1, help_text='显示排序', verbose_name='显示排序')), + ('owner', models.CharField(blank=True, help_text='负责人', max_length=32, null=True, verbose_name='负责人')), + ('phone', models.CharField(blank=True, help_text='联系电话', max_length=32, null=True, verbose_name='联系电话')), + ('email', models.EmailField(blank=True, help_text='邮箱', max_length=32, null=True, verbose_name='邮箱')), + ('status', models.BooleanField(blank=True, default=True, help_text='部门状态', null=True, verbose_name='部门状态')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('parent', models.ForeignKey(blank=True, db_constraint=False, default=None, help_text='上级部门', null=True, on_delete=django.db.models.deletion.CASCADE, to='system.dept', verbose_name='上级部门')), + ], + options={ + 'verbose_name': '部门表', + 'verbose_name_plural': '部门表', + 'db_table': 'pis_system_dept', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='Menu', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('icon', models.CharField(blank=True, help_text='菜单图标', max_length=64, null=True, verbose_name='菜单图标')), + ('name', models.CharField(help_text='菜单名称', max_length=64, verbose_name='菜单名称')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('is_link', models.BooleanField(default=False, help_text='是否外链', verbose_name='是否外链')), + ('link_url', models.CharField(blank=True, help_text='链接地址', max_length=255, null=True, verbose_name='链接地址')), + ('is_catalog', models.BooleanField(default=False, help_text='是否目录', verbose_name='是否目录')), + ('web_path', models.CharField(blank=True, help_text='路由地址', max_length=128, null=True, verbose_name='路由地址')), + ('component', models.CharField(blank=True, help_text='组件地址', max_length=128, null=True, verbose_name='组件地址')), + ('component_name', models.CharField(blank=True, help_text='组件名称', max_length=50, null=True, verbose_name='组件名称')), + ('status', models.BooleanField(blank=True, default=True, help_text='菜单状态', verbose_name='菜单状态')), + ('cache', models.BooleanField(blank=True, default=False, help_text='是否页面缓存', verbose_name='是否页面缓存')), + ('visible', models.BooleanField(blank=True, default=True, help_text='侧边栏中是否显示', verbose_name='侧边栏中是否显示')), + ('is_iframe', models.BooleanField(blank=True, default=False, help_text='框架外显示', verbose_name='框架外显示')), + ('is_affix', models.BooleanField(blank=True, default=False, help_text='是否固定', verbose_name='是否固定')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('parent', models.ForeignKey(blank=True, db_constraint=False, help_text='上级菜单', null=True, on_delete=django.db.models.deletion.CASCADE, to='system.menu', verbose_name='上级菜单')), + ], + options={ + 'verbose_name': '菜单表', + 'verbose_name_plural': '菜单表', + 'db_table': 'pis_system_menu', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='MenuButton', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(help_text='名称', max_length=64, verbose_name='名称')), + ('value', models.CharField(help_text='权限值', max_length=64, unique=True, verbose_name='权限值')), + ('api', models.CharField(help_text='接口地址', max_length=200, verbose_name='接口地址')), + ('method', models.IntegerField(blank=True, default=0, help_text='接口请求方法', null=True, verbose_name='接口请求方法')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('menu', models.ForeignKey(db_constraint=False, help_text='关联菜单', on_delete=django.db.models.deletion.CASCADE, related_name='menuPermission', to='system.menu', verbose_name='关联菜单')), + ], + options={ + 'verbose_name': '菜单权限表', + 'verbose_name_plural': '菜单权限表', + 'db_table': 'pis_system_menu_button', + 'ordering': ('-name',), + }, + ), + migrations.CreateModel( + name='MessageCenter', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('title', models.CharField(help_text='标题', max_length=100, verbose_name='标题')), + ('content', models.TextField(help_text='内容', verbose_name='内容')), + ('target_type', models.IntegerField(default=0, help_text='目标类型', verbose_name='目标类型')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('target_dept', models.ManyToManyField(blank=True, db_constraint=False, help_text='目标部门', to='system.dept', verbose_name='目标部门')), + ], + options={ + 'verbose_name': '消息中心', + 'verbose_name_plural': '消息中心', + 'db_table': 'pis_message_center', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(help_text='角色名称', max_length=64, verbose_name='角色名称')), + ('key', models.CharField(help_text='权限字符', max_length=64, unique=True, verbose_name='权限字符')), + ('sort', models.IntegerField(default=1, help_text='角色顺序', verbose_name='角色顺序')), + ('status', models.BooleanField(default=True, help_text='角色状态', verbose_name='角色状态')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '角色表', + 'verbose_name_plural': '角色表', + 'db_table': 'pis_system_role', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='RoleMenuPermission', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('menu', models.ForeignKey(db_constraint=False, help_text='关联菜单', on_delete=django.db.models.deletion.CASCADE, related_name='role_menu', to='system.menu', verbose_name='关联菜单')), + ('role', models.ForeignKey(db_constraint=False, help_text='关联角色', on_delete=django.db.models.deletion.CASCADE, related_name='role_menu', to='system.role', verbose_name='关联角色')), + ], + options={ + 'verbose_name': '角色菜单权限表', + 'verbose_name_plural': '角色菜单权限表', + 'db_table': 'pis_role_menu_permission', + }, + ), + migrations.CreateModel( + name='RoleMenuButtonPermission', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('data_range', models.IntegerField(choices=[(0, '仅本人数据权限'), (1, '本部门及以下数据权限'), (2, '本部门数据权限'), (3, '全部数据权限'), (4, '自定数据权限')], default=0, help_text='数据权限范围', verbose_name='数据权限范围')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('dept', models.ManyToManyField(blank=True, db_constraint=False, help_text='数据权限-关联部门', to='system.dept', verbose_name='数据权限-关联部门')), + ('menu_button', models.ForeignKey(blank=True, db_constraint=False, help_text='关联菜单按钮', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='menu_button_permission', to='system.menubutton', verbose_name='关联菜单按钮')), + ('role', models.ForeignKey(db_constraint=False, help_text='关联角色', on_delete=django.db.models.deletion.CASCADE, related_name='role_menu_button', to='system.role', verbose_name='关联角色')), + ], + options={ + 'verbose_name': '角色按钮权限表', + 'verbose_name_plural': '角色按钮权限表', + 'db_table': 'pis_role_menu_button_permission', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(help_text='岗位名称', max_length=64, verbose_name='岗位名称')), + ('code', models.CharField(help_text='岗位编码', max_length=32, verbose_name='岗位编码')), + ('sort', models.IntegerField(default=1, help_text='岗位顺序', verbose_name='岗位顺序')), + ('status', models.IntegerField(choices=[(0, '离职'), (1, '在职')], default=1, help_text='岗位状态', verbose_name='岗位状态')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '岗位表', + 'verbose_name_plural': '岗位表', + 'db_table': 'pis_system_post', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='OperationLog', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('request_modular', models.CharField(blank=True, help_text='请求模块', max_length=64, null=True, verbose_name='请求模块')), + ('request_path', models.CharField(blank=True, help_text='请求地址', max_length=400, null=True, verbose_name='请求地址')), + ('request_body', models.TextField(blank=True, help_text='请求参数', null=True, verbose_name='请求参数')), + ('request_method', models.CharField(blank=True, help_text='请求方式', max_length=8, null=True, verbose_name='请求方式')), + ('request_msg', models.TextField(blank=True, help_text='操作说明', null=True, verbose_name='操作说明')), + ('request_ip', models.CharField(blank=True, help_text='请求ip地址', max_length=32, null=True, verbose_name='请求ip地址')), + ('request_browser', models.CharField(blank=True, help_text='请求浏览器', max_length=64, null=True, verbose_name='请求浏览器')), + ('response_code', models.CharField(blank=True, help_text='响应状态码', max_length=32, null=True, verbose_name='响应状态码')), + ('request_os', models.CharField(blank=True, help_text='操作系统', max_length=64, null=True, verbose_name='操作系统')), + ('json_result', models.TextField(blank=True, help_text='返回信息', null=True, verbose_name='返回信息')), + ('status', models.BooleanField(default=False, help_text='响应状态', verbose_name='响应状态')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '操作日志', + 'verbose_name_plural': '操作日志', + 'db_table': 'pis_system_operation_log', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='MessageCenterTargetUser', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('is_read', models.BooleanField(blank=True, default=False, help_text='是否已读', null=True, verbose_name='是否已读')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('messagecenter', models.ForeignKey(db_constraint=False, help_text='关联消息中心表', on_delete=django.db.models.deletion.CASCADE, to='system.messagecenter', verbose_name='关联消息中心表')), + ('users', models.ForeignKey(db_constraint=False, help_text='关联用户表', on_delete=django.db.models.deletion.CASCADE, related_name='target_user', to=settings.AUTH_USER_MODEL, verbose_name='关联用户表')), + ], + options={ + 'verbose_name': '消息中心目标用户表', + 'verbose_name_plural': '消息中心目标用户表', + 'db_table': 'pis_message_center_target_user', + }, + ), + migrations.AddField( + model_name='messagecenter', + name='target_role', + field=models.ManyToManyField(blank=True, db_constraint=False, help_text='目标角色', to='system.role', verbose_name='目标角色'), + ), + migrations.AddField( + model_name='messagecenter', + name='target_user', + field=models.ManyToManyField(blank=True, help_text='目标用户', related_name='user', through='system.MessageCenterTargetUser', to=settings.AUTH_USER_MODEL, verbose_name='目标用户'), + ), + migrations.CreateModel( + name='MenuField', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('model', models.CharField(max_length=64, verbose_name='表名')), + ('field_name', models.CharField(max_length=64, verbose_name='模型表字段名')), + ('title', models.CharField(max_length=64, verbose_name='字段显示名')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('menu', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='system.menu', verbose_name='菜单')), + ], + options={ + 'verbose_name': '菜单字段表', + 'verbose_name_plural': '菜单字段表', + 'db_table': 'pis_system_menu_field', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='LoginLog', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('username', models.CharField(blank=True, help_text='登录用户名', max_length=32, null=True, verbose_name='登录用户名')), + ('ip', models.CharField(blank=True, help_text='登录ip', max_length=32, null=True, verbose_name='登录ip')), + ('agent', models.TextField(blank=True, help_text='agent信息', null=True, verbose_name='agent信息')), + ('browser', models.CharField(blank=True, help_text='浏览器名', max_length=200, null=True, verbose_name='浏览器名')), + ('os', models.CharField(blank=True, help_text='操作系统', max_length=200, null=True, verbose_name='操作系统')), + ('continent', models.CharField(blank=True, help_text='州', max_length=50, null=True, verbose_name='州')), + ('country', models.CharField(blank=True, help_text='国家', max_length=50, null=True, verbose_name='国家')), + ('province', models.CharField(blank=True, help_text='省份', max_length=50, null=True, verbose_name='省份')), + ('city', models.CharField(blank=True, help_text='城市', max_length=50, null=True, verbose_name='城市')), + ('district', models.CharField(blank=True, help_text='县区', max_length=50, null=True, verbose_name='县区')), + ('isp', models.CharField(blank=True, help_text='运营商', max_length=50, null=True, verbose_name='运营商')), + ('area_code', models.CharField(blank=True, help_text='区域代码', max_length=50, null=True, verbose_name='区域代码')), + ('country_english', models.CharField(blank=True, help_text='英文全称', max_length=50, null=True, verbose_name='英文全称')), + ('country_code', models.CharField(blank=True, help_text='简称', max_length=50, null=True, verbose_name='简称')), + ('longitude', models.CharField(blank=True, help_text='经度', max_length=50, null=True, verbose_name='经度')), + ('latitude', models.CharField(blank=True, help_text='纬度', max_length=50, null=True, verbose_name='纬度')), + ('login_type', models.IntegerField(choices=[(1, '普通登录'), (2, '微信扫码登录')], default=1, help_text='登录类型', verbose_name='登录类型')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '登录日志', + 'verbose_name_plural': '登录日志', + 'db_table': 'pis_system_login_log', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='FileList', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(blank=True, help_text='名称', max_length=200, null=True, verbose_name='名称')), + ('url', models.FileField(blank=True, null=True, upload_to=dvadmin.system.models.media_file_name)), + ('file_url', models.CharField(blank=True, help_text='文件地址', max_length=255, verbose_name='文件地址')), + ('engine', models.CharField(blank=True, default='local', help_text='引擎', max_length=100, verbose_name='引擎')), + ('mime_type', models.CharField(blank=True, help_text='Mime类型', max_length=100, verbose_name='Mime类型')), + ('size', models.CharField(blank=True, help_text='文件大小', max_length=36, verbose_name='文件大小')), + ('md5sum', models.CharField(blank=True, help_text='文件md5', max_length=36, verbose_name='文件md5')), + ('upload_method', models.SmallIntegerField(blank=True, choices=[(0, '默认上传'), (1, '文件选择器上传')], default=0, help_text='上传方式', null=True, verbose_name='上传方式')), + ('file_type', models.SmallIntegerField(blank=True, choices=[(0, '图片'), (1, '视频'), (2, '音频'), (3, '其他')], default=3, help_text='文件类型', null=True, verbose_name='文件类型')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '文件管理', + 'verbose_name_plural': '文件管理', + 'db_table': 'pis_system_file_list', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='FieldPermission', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('is_query', models.BooleanField(default=1, verbose_name='是否可查询')), + ('is_create', models.BooleanField(default=1, verbose_name='是否可创建')), + ('is_update', models.BooleanField(default=1, verbose_name='是否可更新')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('field', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='menu_field', to='system.menufield', verbose_name='字段')), + ('role', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='system.role', verbose_name='角色')), + ], + options={ + 'verbose_name': '字段权限表', + 'verbose_name_plural': '字段权限表', + 'db_table': 'pis_system_field_permission', + 'ordering': ('id',), + }, + ), + migrations.CreateModel( + name='DownloadCenter', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('task_name', models.CharField(help_text='任务名称', max_length=255, verbose_name='任务名称')), + ('task_status', models.SmallIntegerField(choices=[(0, '任务已创建'), (1, '任务进行中'), (2, '任务完成'), (3, '任务失败')], default=0, help_text='是否可下载', verbose_name='是否可下载')), + ('file_name', models.CharField(blank=True, help_text='文件名', max_length=255, null=True, verbose_name='文件名')), + ('url', models.FileField(blank=True, null=True, upload_to=dvadmin.system.models.media_file_name_downloadcenter)), + ('size', models.BigIntegerField(default=0, help_text='文件大小', verbose_name='文件大小')), + ('md5sum', models.CharField(blank=True, help_text='文件md5', max_length=36, null=True, verbose_name='文件md5')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '下载中心', + 'verbose_name_plural': '下载中心', + 'db_table': 'pis_download_center', + 'ordering': ('-create_datetime',), + }, + ), + migrations.CreateModel( + name='Dictionary', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('label', models.CharField(blank=True, help_text='字典名称', max_length=100, null=True, verbose_name='字典名称')), + ('value', models.CharField(blank=True, help_text='字典编号/实际值', max_length=200, null=True, verbose_name='字典编号')), + ('type', models.IntegerField(choices=[(0, 'text'), (1, 'number'), (2, 'date'), (3, 'datetime'), (4, 'time'), (5, 'files'), (6, 'boolean'), (7, 'images')], default=0, help_text='数据值类型', verbose_name='数据值类型')), + ('color', models.CharField(blank=True, help_text='颜色', max_length=20, null=True, verbose_name='颜色')), + ('is_value', models.BooleanField(default=False, help_text='是否为value值,用来做具体值存放', verbose_name='是否为value值')), + ('status', models.BooleanField(default=True, help_text='状态', verbose_name='状态')), + ('sort', models.IntegerField(blank=True, default=1, help_text='显示排序', null=True, verbose_name='显示排序')), + ('remark', models.CharField(blank=True, help_text='备注', max_length=2000, null=True, verbose_name='备注')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('parent', models.ForeignKey(blank=True, db_constraint=False, help_text='父级', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sublist', to='system.dictionary', verbose_name='父级')), + ], + options={ + 'verbose_name': '字典表', + 'verbose_name_plural': '字典表', + 'db_table': 'pis_system_dictionary', + 'ordering': ('sort',), + }, + ), + migrations.CreateModel( + name='Area', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('name', models.CharField(help_text='名称', max_length=100, verbose_name='名称')), + ('code', models.CharField(db_index=True, help_text='地区编码', max_length=20, unique=True, verbose_name='地区编码')), + ('level', models.BigIntegerField(help_text='地区层级(1省份 2城市 3区县 4乡级)', verbose_name='地区层级(1省份 2城市 3区县 4乡级)')), + ('pinyin', models.CharField(help_text='拼音', max_length=255, verbose_name='拼音')), + ('initials', models.CharField(help_text='首字母', max_length=20, verbose_name='首字母')), + ('enable', models.BooleanField(default=True, help_text='是否启用', verbose_name='是否启用')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('pcode', models.ForeignKey(blank=True, db_constraint=False, help_text='父地区编码', null=True, on_delete=django.db.models.deletion.CASCADE, to='system.area', to_field='code', verbose_name='父地区编码')), + ], + options={ + 'verbose_name': '地区表', + 'verbose_name_plural': '地区表', + 'db_table': 'pis_system_area', + 'ordering': ('code',), + }, + ), + migrations.CreateModel( + name='ApiWhiteList', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('url', models.CharField(help_text='url地址', max_length=200, verbose_name='url')), + ('method', models.IntegerField(blank=True, default=0, help_text='接口请求方法', null=True, verbose_name='接口请求方法')), + ('enable_datasource', models.BooleanField(blank=True, default=True, help_text='激活数据权限', verbose_name='激活数据权限')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '接口白名单', + 'verbose_name_plural': '接口白名单', + 'db_table': 'pis_api_white_list', + 'ordering': ('-create_datetime',), + }, + ), + migrations.AddField( + model_name='users', + name='current_role', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='当前登录角色', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='current_role_set', to='system.role', verbose_name='当前登录角色'), + ), + migrations.AddField( + model_name='users', + name='dept', + field=models.ForeignKey(blank=True, db_constraint=False, help_text='关联部门', null=True, on_delete=django.db.models.deletion.PROTECT, to='system.dept', verbose_name='所属部门'), + ), + migrations.AddField( + model_name='users', + name='groups', + field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'), + ), + migrations.AddField( + model_name='users', + name='manage_dept', + field=models.ManyToManyField(blank=True, db_constraint=False, help_text='管理部门', related_name='manage_dept_set', to='system.dept', verbose_name='管理部门'), + ), + migrations.AddField( + model_name='users', + name='post', + field=models.ManyToManyField(blank=True, db_constraint=False, help_text='关联岗位', to='system.post', verbose_name='关联岗位'), + ), + migrations.AddField( + model_name='users', + name='role', + field=models.ManyToManyField(blank=True, db_constraint=False, help_text='关联角色', to='system.role', verbose_name='关联角色'), + ), + migrations.AddField( + model_name='users', + name='user_permissions', + field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'), + ), + migrations.CreateModel( + name='SystemConfig', + fields=[ + ('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')), + ('description', models.CharField(blank=True, help_text='描述', max_length=255, null=True, verbose_name='描述')), + ('modifier', models.CharField(blank=True, help_text='修改人', max_length=255, null=True, verbose_name='修改人')), + ('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=255, null=True, verbose_name='数据归属部门')), + ('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')), + ('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')), + ('title', models.CharField(help_text='标题', max_length=50, verbose_name='标题')), + ('key', models.CharField(db_index=True, help_text='键', max_length=100, verbose_name='键')), + ('value', models.JSONField(blank=True, help_text='值', max_length=100, null=True, verbose_name='值')), + ('sort', models.IntegerField(blank=True, default=0, help_text='排序', verbose_name='排序')), + ('status', models.BooleanField(default=True, help_text='启用状态', verbose_name='启用状态')), + ('data_options', models.JSONField(blank=True, help_text='数据options', null=True, verbose_name='数据options')), + ('form_item_type', models.IntegerField(blank=True, choices=[(0, 'text'), (1, 'datetime'), (2, 'date'), (3, 'textarea'), (4, 'select'), (5, 'checkbox'), (6, 'radio'), (7, 'img'), (8, 'file'), (9, 'switch'), (10, 'number'), (11, 'array'), (12, 'imgs'), (13, 'foreignkey'), (14, 'manytomany'), (15, 'time')], default=0, help_text='表单类型', verbose_name='表单类型')), + ('rule', models.JSONField(blank=True, help_text='校验规则', null=True, verbose_name='校验规则')), + ('placeholder', models.CharField(blank=True, help_text='提示信息', max_length=50, null=True, verbose_name='提示信息')), + ('setting', models.JSONField(blank=True, help_text='配置', null=True, verbose_name='配置')), + ('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('parent', models.ForeignKey(blank=True, db_constraint=False, help_text='父级', null=True, on_delete=django.db.models.deletion.CASCADE, to='system.systemconfig', verbose_name='父级')), + ], + options={ + 'verbose_name': '系统配置表', + 'verbose_name_plural': '系统配置表', + 'db_table': 'pis_system_config', + 'ordering': ('sort',), + 'unique_together': {('key', 'parent_id')}, + }, + ), + ] diff --git a/backend/dvadmin/system/migrations/__init__.py b/backend/dvadmin/system/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py new file mode 100644 index 0000000..4dca9c9 --- /dev/null +++ b/backend/dvadmin/system/models.py @@ -0,0 +1,704 @@ +import hashlib +import os +from time import time +from pathlib import PurePosixPath + +from django.contrib.auth.models import AbstractUser, UserManager +from django.db import models +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from application import dispatch +from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models + + +class Role(CoreModel): + name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称") + key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符") + sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序") + status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态") + + class Meta: + db_table = table_prefix + "system_role" + verbose_name = "角色表" + verbose_name_plural = verbose_name + ordering = ("sort",) + + +class CustomUserManager(UserManager): + + def create_superuser(self, username, email=None, password=None, **extra_fields): + user = super(CustomUserManager, self).create_superuser(username, email, password, **extra_fields) + user.set_password(password) + try: + user.role.add(Role.objects.get(name="管理员")) + user.save(using=self._db) + return user + except ObjectDoesNotExist: + user.delete() + raise ValidationError("角色`管理员`不存在, 创建失败, 请先执行python manage.py init") + + +class Users(CoreModel, AbstractUser): + username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号", + help_text="用户账号") + email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱") + mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话") + avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像") + name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名") + GENDER_CHOICES = ( + (0, "未知"), + (1, "男"), + (2, "女"), + ) + gender = models.IntegerField( + choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True, help_text="性别" + ) + USER_TYPE = ( + (0, "后台用户"), + (1, "前台用户"), + ) + user_type = models.IntegerField( + choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, help_text="用户类型" + ) + post = models.ManyToManyField(to="Post", blank=True, verbose_name="关联岗位", db_constraint=False, + help_text="关联岗位") + role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False, + help_text="关联角色") + current_role = models.ForeignKey(to=Role, null=True, blank=True, db_constraint=False, on_delete=models.SET_NULL, + verbose_name="当前登录角色", help_text="当前登录角色", related_name='current_role_set') + dept = models.ForeignKey( + to="Dept", + verbose_name="所属部门", + on_delete=models.PROTECT, + db_constraint=False, + null=True, + blank=True, + help_text="关联部门", + ) + manage_dept = models.ManyToManyField( + to="Dept", + verbose_name="管理部门", + db_constraint=False, + blank=True, + help_text="管理部门", + related_name='manage_dept_set' + ) + login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数") + pwd_change_count = models.IntegerField(default=0,blank=True, verbose_name="密码修改次数", help_text="密码修改次数") + objects = CustomUserManager() + + def set_password(self, raw_password): + if raw_password: + super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest()) + + def save(self, *args, **kwargs): + if self.name == "": + self.name = self.username + super().save(*args, **kwargs) + + class Meta: + db_table = table_prefix + "system_users" + verbose_name = "用户表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class Post(CoreModel): + name = models.CharField(null=False, max_length=64, verbose_name="岗位名称", help_text="岗位名称") + code = models.CharField(max_length=32, verbose_name="岗位编码", help_text="岗位编码") + sort = models.IntegerField(default=1, verbose_name="岗位顺序", help_text="岗位顺序") + STATUS_CHOICES = ( + (0, "离职"), + (1, "在职"), + ) + status = models.IntegerField(choices=STATUS_CHOICES, default=1, verbose_name="岗位状态", help_text="岗位状态") + + class Meta: + db_table = table_prefix + "system_post" + verbose_name = "岗位表" + verbose_name_plural = verbose_name + ordering = ("sort",) + + +class Dept(CoreModel): + name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称") + key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", help_text="关联字符") + sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序") + owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人") + phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话") + email = models.EmailField(max_length=32, verbose_name="邮箱", null=True, blank=True, help_text="邮箱") + status = models.BooleanField(default=True, verbose_name="部门状态", null=True, blank=True, help_text="部门状态") + parent = models.ForeignKey( + to="Dept", + on_delete=models.CASCADE, + default=None, + verbose_name="上级部门", + db_constraint=False, + null=True, + blank=True, + help_text="上级部门", + ) + + @classmethod + def _recursion(cls, instance, parent, result): + new_instance = getattr(instance, parent, None) + res = [] + data = getattr(instance, result, None) + if data: + res.append(data) + if new_instance: + array = cls._recursion(new_instance, parent, result) + res += array + return res + + @classmethod + def get_region_name(cls, obj): + """ + 获取某个用户的递归所有部门名称 + """ + dept_name_all = cls._recursion(obj, "parent", "name") + dept_name_all.reverse() + return "/".join(dept_name_all) + + @classmethod + def recursion_all_dept(cls, dept_id: int, dept_all_list=None, dept_list=None): + """ + 递归获取部门的所有下级部门 + :param dept_id: 需要获取的id + :param dept_all_list: 所有列表 + :param dept_list: 递归list + :return: + """ + if not dept_all_list: + dept_all_list = Dept.objects.values("id", "parent") + if dept_list is None: + dept_list = [dept_id] + for ele in dept_all_list: + if ele.get("parent") == dept_id: + dept_list.append(ele.get("id")) + cls.recursion_all_dept(ele.get("id"), dept_all_list, dept_list) + return list(set(dept_list)) + + @classmethod + def recursion_all_parent_dept(cls, dept_id: int, dept_list=None): + """ + 递归获取部门的所有上级部门 + :param dept_id: 需要获取的id + :param dept_list: 递归list + :return: + """ + if dept_list is None: + dept_list = [dept_id] + current_dept = Dept.objects.filter(id=dept_id).values('parent').first() + if current_dept and current_dept.get('parent'): + parent_id = current_dept.get('parent') + dept_list.append(parent_id) + cls.recursion_all_parent_dept(parent_id, dept_list) + return list(set(dept_list)) + + class Meta: + db_table = table_prefix + "system_dept" + verbose_name = "部门表" + verbose_name_plural = verbose_name + ordering = ("sort",) + + +class Menu(CoreModel): + parent = models.ForeignKey( + to="Menu", + on_delete=models.CASCADE, + verbose_name="上级菜单", + null=True, + blank=True, + db_constraint=False, + help_text="上级菜单", + ) + icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标") + name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称") + sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序") + ISLINK_CHOICES = ( + (0, "否"), + (1, "是"), + ) + is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链") + link_url = models.CharField(max_length=255, verbose_name="链接地址", null=True, blank=True, help_text="链接地址") + is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录") + web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址") + component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址") + component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True, + help_text="组件名称") + status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态") + cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存") + visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示", + help_text="侧边栏中是否显示") + is_iframe = models.BooleanField(default=False, blank=True, verbose_name="框架外显示", help_text="框架外显示") + is_affix = models.BooleanField(default=False, blank=True, verbose_name="是否固定", help_text="是否固定") + + @classmethod + def get_all_parent(cls, id: int, all_list=None, nodes=None): + """ + 递归获取给定ID的所有层级 + :param id: 参数ID + :param all_list: 所有列表 + :param nodes: 递归列表 + :return: nodes + """ + if not all_list: + all_list = Menu.objects.values("id", "name", "parent") + if nodes is None: + nodes = [] + for ele in all_list: + if ele.get("id") == id: + parent_id = ele.get("parent") + if parent_id is not None: + cls.get_all_parent(parent_id, all_list, nodes) + nodes.append(ele) + return nodes + class Meta: + db_table = table_prefix + "system_menu" + verbose_name = "菜单表" + verbose_name_plural = verbose_name + ordering = ("sort",) + +class MenuField(CoreModel): + model = models.CharField(max_length=64, verbose_name='表名') + menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False) + field_name = models.CharField(max_length=64, verbose_name='模型表字段名') + title = models.CharField(max_length=64, verbose_name='字段显示名') + class Meta: + db_table = table_prefix + "system_menu_field" + verbose_name = "菜单字段表" + verbose_name_plural = verbose_name + ordering = ("id",) + +class FieldPermission(CoreModel): + role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False) + field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False) + is_query = models.BooleanField(default=1, verbose_name='是否可查询') + is_create = models.BooleanField(default=1, verbose_name='是否可创建') + is_update = models.BooleanField(default=1, verbose_name='是否可更新') + + class Meta: + db_table = table_prefix + "system_field_permission" + verbose_name = "字段权限表" + verbose_name_plural = verbose_name + ordering = ("id",) + + +class MenuButton(CoreModel): + menu = models.ForeignKey( + to="Menu", + db_constraint=False, + related_name="menuPermission", + on_delete=models.CASCADE, + verbose_name="关联菜单", + help_text="关联菜单", + ) + name = models.CharField(max_length=64, verbose_name="名称", help_text="名称") + value = models.CharField(unique=True, max_length=64, verbose_name="权限值", help_text="权限值") + api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址") + METHOD_CHOICES = ( + (0, "GET"), + (1, "POST"), + (2, "PUT"), + (3, "DELETE"), + ) + method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True, + help_text="接口请求方法") + + class Meta: + db_table = table_prefix + "system_menu_button" + verbose_name = "菜单权限表" + verbose_name_plural = verbose_name + ordering = ("-name",) + + +class RoleMenuPermission(CoreModel): + role = models.ForeignKey( + to="Role", + db_constraint=False, + related_name="role_menu", + on_delete=models.CASCADE, + verbose_name="关联角色", + help_text="关联角色", + ) + menu = models.ForeignKey( + to="Menu", + db_constraint=False, + related_name="role_menu", + on_delete=models.CASCADE, + verbose_name="关联菜单", + help_text="关联菜单", + ) + + class Meta: + db_table = table_prefix + "role_menu_permission" + verbose_name = "角色菜单权限表" + verbose_name_plural = verbose_name + # ordering = ("-create_datetime",) + + +class RoleMenuButtonPermission(CoreModel): + role = models.ForeignKey( + to="Role", + db_constraint=False, + related_name="role_menu_button", + on_delete=models.CASCADE, + verbose_name="关联角色", + help_text="关联角色", + ) + menu_button = models.ForeignKey( + to="MenuButton", + db_constraint=False, + related_name="menu_button_permission", + on_delete=models.CASCADE, + verbose_name="关联菜单按钮", + help_text="关联菜单按钮", + null=True, + blank=True + ) + DATASCOPE_CHOICES = ( + (0, "仅本人数据权限"), + (1, "本部门及以下数据权限"), + (2, "本部门数据权限"), + (3, "全部数据权限"), + (4, "自定数据权限"), + ) + data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围", + help_text="数据权限范围") + dept = models.ManyToManyField(to="Dept", blank=True, verbose_name="数据权限-关联部门", db_constraint=False, + help_text="数据权限-关联部门") + + class Meta: + db_table = table_prefix + "role_menu_button_permission" + verbose_name = "角色按钮权限表" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class Dictionary(CoreModel): + TYPE_LIST = ( + (0, "text"), + (1, "number"), + (2, "date"), + (3, "datetime"), + (4, "time"), + (5, "files"), + (6, "boolean"), + (7, "images"), + ) + label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称") + value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值") + parent = models.ForeignKey( + to="self", + related_name="sublist", + db_constraint=False, + on_delete=models.PROTECT, + blank=True, + null=True, + verbose_name="父级", + help_text="父级", + ) + type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型") + color = models.CharField(max_length=20, blank=True, null=True, verbose_name="颜色", help_text="颜色") + is_value = models.BooleanField(default=False, verbose_name="是否为value值", + help_text="是否为value值,用来做具体值存放") + status = models.BooleanField(default=True, verbose_name="状态", help_text="状态") + sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序") + remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注") + + class Meta: + db_table = table_prefix + "system_dictionary" + verbose_name = "字典表" + verbose_name_plural = verbose_name + ordering = ("sort",) + + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + super().save(force_insert, force_update, using, update_fields) + dispatch.refresh_dictionary() # 有更新则刷新字典配置 + + def delete(self, using=None, keep_parents=False): + res = super().delete(using, keep_parents) + dispatch.refresh_dictionary() + return res + + +class OperationLog(CoreModel): + request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True, + help_text="请求模块") + request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True, + help_text="请求地址") + request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数") + request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True, + help_text="请求方式") + request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明") + request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True, + help_text="请求ip地址") + request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True, + help_text="请求浏览器") + response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True, + help_text="响应状态码") + request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统") + json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息") + status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态") + + class Meta: + db_table = table_prefix + "system_operation_log" + verbose_name = "操作日志" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +def media_file_name(instance, filename): + h = instance.md5sum + basename, ext = os.path.splitext(filename) + return os.path.join("files", h[:1], h[1:2], h + ext.lower()) + + +class FileList(CoreModel): + name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称") + url = models.FileField(upload_to=media_file_name, null=True, blank=True,) + file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址") + engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎") + mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型") + size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小") + md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5") + UPLOAD_METHOD_CHOIDES = ( + (0, '默认上传'), + (1, '文件选择器上传'), + ) + upload_method = models.SmallIntegerField(default=0, blank=True, null=True, choices=UPLOAD_METHOD_CHOIDES, verbose_name='上传方式', help_text='上传方式') + FILE_TYPE_CHOIDES = ( + (0, '图片'), + (1, '视频'), + (2, '音频'), + (3, '其他'), + ) + file_type = models.SmallIntegerField(default=3, choices=FILE_TYPE_CHOIDES, blank=True, null=True, verbose_name='文件类型', help_text='文件类型') + + def save(self, *args, **kwargs): + if not self.md5sum: # file is new + md5 = hashlib.md5() + for chunk in self.url.chunks(): + md5.update(chunk) + self.md5sum = md5.hexdigest() + if not self.size: + self.size = self.url.size + if not self.file_url: + url = media_file_name(self, self.name) + self.file_url = f'media/{url}' + super(FileList, self).save(*args, **kwargs) + + class Meta: + db_table = table_prefix + "system_file_list" + verbose_name = "文件管理" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class Area(CoreModel): + name = models.CharField(max_length=100, verbose_name="名称", help_text="名称") + code = models.CharField(max_length=20, verbose_name="地区编码", help_text="地区编码", unique=True, db_index=True) + level = models.BigIntegerField(verbose_name="地区层级(1省份 2城市 3区县 4乡级)", + help_text="地区层级(1省份 2城市 3区县 4乡级)") + pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音") + initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母") + enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用") + pcode = models.ForeignKey( + to="self", + verbose_name="父地区编码", + to_field="code", + on_delete=models.CASCADE, + db_constraint=False, + null=True, + blank=True, + help_text="父地区编码", + ) + + class Meta: + db_table = table_prefix + "system_area" + verbose_name = "地区表" + verbose_name_plural = verbose_name + ordering = ("code",) + + def __str__(self): + return f"{self.name}" + + +class ApiWhiteList(CoreModel): + url = models.CharField(max_length=200, help_text="url地址", verbose_name="url") + METHOD_CHOICES = ( + (0, "GET"), + (1, "POST"), + (2, "PUT"), + (3, "DELETE"), + ) + method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True, + help_text="接口请求方法") + enable_datasource = models.BooleanField(default=True, verbose_name="激活数据权限", help_text="激活数据权限", + blank=True) + + class Meta: + db_table = table_prefix + "api_white_list" + verbose_name = "接口白名单" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class SystemConfig(CoreModel): + parent = models.ForeignKey( + to="self", + verbose_name="父级", + on_delete=models.CASCADE, + db_constraint=False, + null=True, + blank=True, + help_text="父级", + ) + title = models.CharField(max_length=50, verbose_name="标题", help_text="标题") + key = models.CharField(max_length=100, verbose_name="键", help_text="键", db_index=True) + value = models.JSONField(max_length=100, verbose_name="值", help_text="值", null=True, blank=True) + sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True) + status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态") + data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True) + FORM_ITEM_TYPE_LIST = ( + (0, "text"), + (1, "datetime"), + (2, "date"), + (3, "textarea"), + (4, "select"), + (5, "checkbox"), + (6, "radio"), + (7, "img"), + (8, "file"), + (9, "switch"), + (10, "number"), + (11, "array"), + (12, "imgs"), + (13, "foreignkey"), + (14, "manytomany"), + (15, "time"), + ) + form_item_type = models.IntegerField( + choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, blank=True + ) + rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则") + placeholder = models.CharField(max_length=50, null=True, blank=True, verbose_name="提示信息", help_text="提示信息") + setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置") + + class Meta: + db_table = table_prefix + "system_config" + verbose_name = "系统配置表" + verbose_name_plural = verbose_name + ordering = ("sort",) + unique_together = (("key", "parent_id"),) + + def __str__(self): + return f"{self.title}" + + def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + super().save(force_insert, force_update, using, update_fields) + dispatch.refresh_system_config() # 有更新则刷新系统配置 + + def delete(self, using=None, keep_parents=False): + res = super().delete(using, keep_parents) + dispatch.refresh_system_config() + return res + + +class LoginLog(CoreModel): + LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"),) + username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名") + ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip") + agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息") + browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名") + os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统") + continent = models.CharField(max_length=50, verbose_name="州", null=True, blank=True, help_text="州") + country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家") + province = models.CharField(max_length=50, verbose_name="省份", null=True, blank=True, help_text="省份") + city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True, help_text="城市") + district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区") + isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商") + area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码") + country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True, + help_text="英文全称") + country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称") + longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度") + latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度") + login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", + help_text="登录类型") + + class Meta: + db_table = table_prefix + "system_login_log" + verbose_name = "登录日志" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class MessageCenter(CoreModel): + title = models.CharField(max_length=100, verbose_name="标题", help_text="标题") + content = models.TextField(verbose_name="内容", help_text="内容") + target_type = models.IntegerField(default=0, verbose_name="目标类型", help_text="目标类型") + target_user = models.ManyToManyField(to=Users, related_name='user', through='MessageCenterTargetUser', + through_fields=('messagecenter', 'users'), blank=True, verbose_name="目标用户", + help_text="目标用户") + target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False, + verbose_name="目标部门", help_text="目标部门") + target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False, + verbose_name="目标角色", help_text="目标角色") + + class Meta: + db_table = table_prefix + "message_center" + verbose_name = "消息中心" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + + +class MessageCenterTargetUser(CoreModel): + users = models.ForeignKey(Users, related_name="target_user", on_delete=models.CASCADE, db_constraint=False, + verbose_name="关联用户表", help_text="关联用户表") + messagecenter = models.ForeignKey(MessageCenter, on_delete=models.CASCADE, db_constraint=False, + verbose_name="关联消息中心表", help_text="关联消息中心表") + is_read = models.BooleanField(default=False, blank=True, null=True, verbose_name="是否已读", help_text="是否已读") + + class Meta: + db_table = table_prefix + "message_center_target_user" + verbose_name = "消息中心目标用户表" + verbose_name_plural = verbose_name + + +def media_file_name_downloadcenter(instance:'DownloadCenter', filename): + h = instance.md5sum + basename, ext = os.path.splitext(filename) + return PurePosixPath("files", "dlct", h[:1], h[1:2], basename + '-' + str(time()).replace('.', '') + ext.lower()) + + +class DownloadCenter(CoreModel): + TASK_STATUS_CHOICES = [ + (0, '任务已创建'), + (1, '任务进行中'), + (2, '任务完成'), + (3, '任务失败'), + ] + task_name = models.CharField(max_length=255, verbose_name="任务名称", help_text="任务名称") + task_status = models.SmallIntegerField(default=0, choices=TASK_STATUS_CHOICES, verbose_name='是否可下载', help_text='是否可下载') + file_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="文件名", help_text="文件名") + url = models.FileField(upload_to=media_file_name_downloadcenter, null=True, blank=True) + size = models.BigIntegerField(default=0, verbose_name="文件大小", help_text="文件大小") + md5sum = models.CharField(max_length=36, null=True, blank=True, verbose_name="文件md5", help_text="文件md5") + + def save(self, *args, **kwargs): + if self.url: + if not self.md5sum: # file is new + md5 = hashlib.md5() + for chunk in self.url.chunks(): + md5.update(chunk) + self.md5sum = md5.hexdigest() + if not self.size: + self.size = self.url.size + super(DownloadCenter, self).save(*args, **kwargs) + + class Meta: + db_table = table_prefix + "download_center" + verbose_name = "下载中心" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) diff --git a/backend/dvadmin/system/signals.py b/backend/dvadmin/system/signals.py new file mode 100644 index 0000000..d00770c --- /dev/null +++ b/backend/dvadmin/system/signals.py @@ -0,0 +1,27 @@ +import time + +from django.db.models.signals import post_save, post_delete +from django.dispatch import Signal, receiver +from django.core.cache import cache +from dvadmin.system.models import MessageCenterTargetUser + +# 初始化信号 +pre_init_complete = Signal() +detail_init_complete = Signal() +post_init_complete = Signal() +# 租户初始化信号 +pre_tenants_init_complete = Signal() +detail_tenants_init_complete = Signal() +post_tenants_init_complete = Signal() +post_tenants_all_init_complete = Signal() +# 租户创建完成信号 +tenants_create_complete = Signal() + +# 全局变量用于标记最后修改时间 +last_db_change_time = time.time() + + +@receiver(post_save, sender=MessageCenterTargetUser) +@receiver(post_delete, sender=MessageCenterTargetUser) +def update_last_change_time(sender, **kwargs): + cache.set('last_db_change_time', time.time(), timeout=None) # 设置永不超时的键值对 diff --git a/backend/dvadmin/system/tasks.py b/backend/dvadmin/system/tasks.py new file mode 100644 index 0000000..0245e63 --- /dev/null +++ b/backend/dvadmin/system/tasks.py @@ -0,0 +1,107 @@ +from hashlib import md5 +from io import BytesIO +from datetime import datetime +from time import sleep + +from openpyxl import Workbook +from openpyxl.worksheet.table import Table, TableStyleInfo +from openpyxl.utils import get_column_letter +from django.core.files.base import ContentFile + +from application.celery import app +from dvadmin.system.models import DownloadCenter + +def is_number(num): + try: + float(num) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(num) + return True + except (TypeError, ValueError): + pass + return False + +def get_string_len(string): + """ + 获取字符串最大长度 + :param string: + :return: + """ + length = 4 + if string is None: + return length + if is_number(string): + return length + for char in string: + length += 2.1 if ord(char) > 256 else 1 + return round(length, 1) if length <= 50 else 50 + +@app.task +def async_export_data(data: list, filename: str, dcid: int, export_field_label: dict): + instance = DownloadCenter.objects.get(pk=dcid) + instance.task_status = 1 + instance.save() + sleep(2) + try: + wb = Workbook() + ws = wb.active + header_data = ["序号", *export_field_label.values()] + hidden_header = ["#", *export_field_label.keys()] + df_len_max = [get_string_len(ele) for ele in header_data] + row = get_column_letter(len(export_field_label) + 1) + column = 1 + ws.append(header_data) + for index, results in enumerate(data): + results_list = [] + for h_index, h_item in enumerate(hidden_header): + for key, val in results.items(): + if key == h_item: + if val is None or val == "": + results_list.append("") + elif isinstance(val, datetime): + val = val.strftime("%Y-%m-%d %H:%M:%S") + results_list.append(val) + else: + results_list.append(val) + # 计算最大列宽度 + result_column_width = get_string_len(val) + if h_index != 0 and result_column_width > df_len_max[h_index]: + df_len_max[h_index] = result_column_width + ws.append([index + 1, *results_list]) + column += 1 + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) + tab.tableStyleInfo = style + ws.add_table(tab) + stream = BytesIO() + wb.save(stream) + stream.seek(0) + s = md5() + while True: + chunk = stream.read(1024) + if not chunk: + break + s.update(chunk) + stream.seek(0) + instance.md5sum = s.hexdigest() + instance.file_name = filename + instance.url.save(filename, ContentFile(stream.read())) + instance.task_status = 2 + except Exception as e: + instance.task_status = 3 + instance.description = str(e)[:250] + instance.save() diff --git a/backend/dvadmin/system/tests.py b/backend/dvadmin/system/tests.py new file mode 100644 index 0000000..866d09d --- /dev/null +++ b/backend/dvadmin/system/tests.py @@ -0,0 +1,56 @@ +from functools import wraps + +from django.db.models import Func, F, OuterRef, Exists +from django.test import TestCase +import django +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings") +django.setup() +from dvadmin.system.models import Menu, RoleMenuPermission, RoleMenuButtonPermission, MenuButton + + +import time + +def timing_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + run_time = end_time - start_time + print(f"{func.__name__} ran in {run_time:.6f} seconds") + return result + return wrapper + +@timing_decorator +def getMenu(): + data = [] + queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id') + for item in queryset: + parent_list = Menu.get_all_parent(item['id']) + names = [d["name"] for d in parent_list] + completeName = "/".join(names) + isCheck = RoleMenuPermission.objects.filter( + menu__id=item['id'], + role__id=1, + ).exists() + mbCheck = RoleMenuButtonPermission.objects.filter( + menu_button = OuterRef("pk"), + role__id=1, + ) + btns = MenuButton.objects.filter( + menu__id=item['id'], + ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',data_range=F('menu_button_permission__data_range')) + # print(b) + dicts = { + 'name': completeName, + 'id': item['id'], + 'isCheck': isCheck, + 'btns':btns + } + print(dicts) + data.append(dicts) + # print(data) + +if __name__ == '__main__': + getMenu() \ No newline at end of file diff --git a/backend/dvadmin/system/urls.py b/backend/dvadmin/system/urls.py new file mode 100644 index 0000000..7c0afd3 --- /dev/null +++ b/backend/dvadmin/system/urls.py @@ -0,0 +1,60 @@ +from django.urls import path, include +from rest_framework import routers + +from dvadmin.system.views.api_white_list import ApiWhiteListViewSet +from dvadmin.system.views.area import AreaViewSet +from dvadmin.system.views.clause import PrivacyView, TermsServiceView +from dvadmin.system.views.dept import DeptViewSet +from dvadmin.system.views.dictionary import DictionaryViewSet +from dvadmin.system.views.file_list import FileViewSet +from dvadmin.system.views.login import SupplierLoginView +from dvadmin.system.views.login_log import LoginLogViewSet +from dvadmin.system.views.menu import MenuViewSet +from dvadmin.system.views.menu_button import MenuButtonViewSet +from dvadmin.system.views.message_center import MessageCenterViewSet +from dvadmin.system.views.operation_log import OperationLogViewSet +from dvadmin.system.views.role import RoleViewSet +from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet +from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet +from dvadmin.system.views.system_config import SystemConfigViewSet +from dvadmin.system.views.user import UserViewSet +from dvadmin.system.views.menu_field import MenuFieldViewSet +from dvadmin.system.views.download_center import DownloadCenterViewSet + + +system_url = routers.SimpleRouter() +system_url.register(r'menu', MenuViewSet) +system_url.register(r'menu_button', MenuButtonViewSet) +system_url.register(r'role', RoleViewSet) +system_url.register(r'dept', DeptViewSet) +system_url.register(r'user', UserViewSet) +system_url.register(r'operation_log', OperationLogViewSet) +system_url.register(r'dictionary', DictionaryViewSet) +system_url.register(r'area', AreaViewSet) +system_url.register(r'file', FileViewSet) +system_url.register(r'api_white_list', ApiWhiteListViewSet) +system_url.register(r'system_config', SystemConfigViewSet) +system_url.register(r'message_center', MessageCenterViewSet) +system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet) +system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet) +system_url.register(r'column', MenuFieldViewSet) +system_url.register(r'login_log', LoginLogViewSet) +system_url.register(r'download_center', DownloadCenterViewSet) + + +urlpatterns = [ + path('user/export/', UserViewSet.as_view({'post': 'export_data', })), + path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})), + path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})), + path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})), + path('system_config/get_table_data//', SystemConfigViewSet.as_view({'get': 'get_table_data'})), + path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})), + # path('login_log/', LoginLogViewSet.as_view({'get': 'list'})), + # path('login_log//', LoginLogViewSet.as_view({'get': 'retrieve'})), + # path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})), + path('clause/privacy.html', PrivacyView.as_view()), + path('clause/terms_service.html', TermsServiceView.as_view()), + # 供应商登录 + path('login/supplier/', SupplierLoginView.as_view(), name='supplier_login'), +] +urlpatterns += system_url.urls diff --git a/backend/dvadmin/system/util/pca-code.json b/backend/dvadmin/system/util/pca-code.json new file mode 100644 index 0000000..36763ba --- /dev/null +++ b/backend/dvadmin/system/util/pca-code.json @@ -0,0 +1 @@ +[{"code":"11","name":"北京市","children":[{"code":"1101","name":"市辖区","children":[{"code":"110101","name":"东城区"},{"code":"110102","name":"西城区"},{"code":"110105","name":"朝阳区"},{"code":"110106","name":"丰台区"},{"code":"110107","name":"石景山区"},{"code":"110108","name":"海淀区"},{"code":"110109","name":"门头沟区"},{"code":"110111","name":"房山区"},{"code":"110112","name":"通州区"},{"code":"110113","name":"顺义区"},{"code":"110114","name":"昌平区"},{"code":"110115","name":"大兴区"},{"code":"110116","name":"怀柔区"},{"code":"110117","name":"平谷区"},{"code":"110118","name":"密云区"},{"code":"110119","name":"延庆区"}]}]},{"code":"12","name":"天津市","children":[{"code":"1201","name":"市辖区","children":[{"code":"120101","name":"和平区"},{"code":"120102","name":"河东区"},{"code":"120103","name":"河西区"},{"code":"120104","name":"南开区"},{"code":"120105","name":"河北区"},{"code":"120106","name":"红桥区"},{"code":"120110","name":"东丽区"},{"code":"120111","name":"西青区"},{"code":"120112","name":"津南区"},{"code":"120113","name":"北辰区"},{"code":"120114","name":"武清区"},{"code":"120115","name":"宝坻区"},{"code":"120116","name":"滨海新区"},{"code":"120117","name":"宁河区"},{"code":"120118","name":"静海区"},{"code":"120119","name":"蓟州区"}]}]},{"code":"13","name":"河北省","children":[{"code":"1301","name":"石家庄市","children":[{"code":"130102","name":"长安区"},{"code":"130104","name":"桥西区"},{"code":"130105","name":"新华区"},{"code":"130107","name":"井陉矿区"},{"code":"130108","name":"裕华区"},{"code":"130109","name":"藁城区"},{"code":"130110","name":"鹿泉区"},{"code":"130111","name":"栾城区"},{"code":"130121","name":"井陉县"},{"code":"130123","name":"正定县"},{"code":"130125","name":"行唐县"},{"code":"130126","name":"灵寿县"},{"code":"130127","name":"高邑县"},{"code":"130128","name":"深泽县"},{"code":"130129","name":"赞皇县"},{"code":"130130","name":"无极县"},{"code":"130131","name":"平山县"},{"code":"130132","name":"元氏县"},{"code":"130133","name":"赵县"},{"code":"130171","name":"石家庄高新技术产业开发区"},{"code":"130172","name":"石家庄循环化工园区"},{"code":"130181","name":"辛集市"},{"code":"130183","name":"晋州市"},{"code":"130184","name":"新乐市"}]},{"code":"1302","name":"唐山市","children":[{"code":"130202","name":"路南区"},{"code":"130203","name":"路北区"},{"code":"130204","name":"古冶区"},{"code":"130205","name":"开平区"},{"code":"130207","name":"丰南区"},{"code":"130208","name":"丰润区"},{"code":"130209","name":"曹妃甸区"},{"code":"130224","name":"滦南县"},{"code":"130225","name":"乐亭县"},{"code":"130227","name":"迁西县"},{"code":"130229","name":"玉田县"},{"code":"130271","name":"河北唐山芦台经济开发区"},{"code":"130272","name":"唐山市汉沽管理区"},{"code":"130273","name":"唐山高新技术产业开发区"},{"code":"130274","name":"河北唐山海港经济开发区"},{"code":"130281","name":"遵化市"},{"code":"130283","name":"迁安市"},{"code":"130284","name":"滦州市"}]},{"code":"1303","name":"秦皇岛市","children":[{"code":"130302","name":"海港区"},{"code":"130303","name":"山海关区"},{"code":"130304","name":"北戴河区"},{"code":"130306","name":"抚宁区"},{"code":"130321","name":"青龙满族自治县"},{"code":"130322","name":"昌黎县"},{"code":"130324","name":"卢龙县"},{"code":"130371","name":"秦皇岛市经济技术开发区"},{"code":"130372","name":"北戴河新区"}]},{"code":"1304","name":"邯郸市","children":[{"code":"130402","name":"邯山区"},{"code":"130403","name":"丛台区"},{"code":"130404","name":"复兴区"},{"code":"130406","name":"峰峰矿区"},{"code":"130407","name":"肥乡区"},{"code":"130408","name":"永年区"},{"code":"130423","name":"临漳县"},{"code":"130424","name":"成安县"},{"code":"130425","name":"大名县"},{"code":"130426","name":"涉县"},{"code":"130427","name":"磁县"},{"code":"130430","name":"邱县"},{"code":"130431","name":"鸡泽县"},{"code":"130432","name":"广平县"},{"code":"130433","name":"馆陶县"},{"code":"130434","name":"魏县"},{"code":"130435","name":"曲周县"},{"code":"130471","name":"邯郸经济技术开发区"},{"code":"130473","name":"邯郸冀南新区"},{"code":"130481","name":"武安市"}]},{"code":"1305","name":"邢台市","children":[{"code":"130502","name":"襄都区"},{"code":"130503","name":"信都区"},{"code":"130505","name":"任泽区"},{"code":"130506","name":"南和区"},{"code":"130522","name":"临城县"},{"code":"130523","name":"内丘县"},{"code":"130524","name":"柏乡县"},{"code":"130525","name":"隆尧县"},{"code":"130528","name":"宁晋县"},{"code":"130529","name":"巨鹿县"},{"code":"130530","name":"新河县"},{"code":"130531","name":"广宗县"},{"code":"130532","name":"平乡县"},{"code":"130533","name":"威县"},{"code":"130534","name":"清河县"},{"code":"130535","name":"临西县"},{"code":"130571","name":"河北邢台经济开发区"},{"code":"130581","name":"南宫市"},{"code":"130582","name":"沙河市"}]},{"code":"1306","name":"保定市","children":[{"code":"130602","name":"竞秀区"},{"code":"130606","name":"莲池区"},{"code":"130607","name":"满城区"},{"code":"130608","name":"清苑区"},{"code":"130609","name":"徐水区"},{"code":"130623","name":"涞水县"},{"code":"130624","name":"阜平县"},{"code":"130626","name":"定兴县"},{"code":"130627","name":"唐县"},{"code":"130628","name":"高阳县"},{"code":"130629","name":"容城县"},{"code":"130630","name":"涞源县"},{"code":"130631","name":"望都县"},{"code":"130632","name":"安新县"},{"code":"130633","name":"易县"},{"code":"130634","name":"曲阳县"},{"code":"130635","name":"蠡县"},{"code":"130636","name":"顺平县"},{"code":"130637","name":"博野县"},{"code":"130638","name":"雄县"},{"code":"130671","name":"保定高新技术产业开发区"},{"code":"130672","name":"保定白沟新城"},{"code":"130681","name":"涿州市"},{"code":"130682","name":"定州市"},{"code":"130683","name":"安国市"},{"code":"130684","name":"高碑店市"}]},{"code":"1307","name":"张家口市","children":[{"code":"130702","name":"桥东区"},{"code":"130703","name":"桥西区"},{"code":"130705","name":"宣化区"},{"code":"130706","name":"下花园区"},{"code":"130708","name":"万全区"},{"code":"130709","name":"崇礼区"},{"code":"130722","name":"张北县"},{"code":"130723","name":"康保县"},{"code":"130724","name":"沽源县"},{"code":"130725","name":"尚义县"},{"code":"130726","name":"蔚县"},{"code":"130727","name":"阳原县"},{"code":"130728","name":"怀安县"},{"code":"130730","name":"怀来县"},{"code":"130731","name":"涿鹿县"},{"code":"130732","name":"赤城县"},{"code":"130771","name":"张家口经济开发区"},{"code":"130772","name":"张家口市察北管理区"},{"code":"130773","name":"张家口市塞北管理区"}]},{"code":"1308","name":"承德市","children":[{"code":"130802","name":"双桥区"},{"code":"130803","name":"双滦区"},{"code":"130804","name":"鹰手营子矿区"},{"code":"130821","name":"承德县"},{"code":"130822","name":"兴隆县"},{"code":"130824","name":"滦平县"},{"code":"130825","name":"隆化县"},{"code":"130826","name":"丰宁满族自治县"},{"code":"130827","name":"宽城满族自治县"},{"code":"130828","name":"围场满族蒙古族自治县"},{"code":"130871","name":"承德高新技术产业开发区"},{"code":"130881","name":"平泉市"}]},{"code":"1309","name":"沧州市","children":[{"code":"130902","name":"新华区"},{"code":"130903","name":"运河区"},{"code":"130921","name":"沧县"},{"code":"130922","name":"青县"},{"code":"130923","name":"东光县"},{"code":"130924","name":"海兴县"},{"code":"130925","name":"盐山县"},{"code":"130926","name":"肃宁县"},{"code":"130927","name":"南皮县"},{"code":"130928","name":"吴桥县"},{"code":"130929","name":"献县"},{"code":"130930","name":"孟村回族自治县"},{"code":"130971","name":"河北沧州经济开发区"},{"code":"130972","name":"沧州高新技术产业开发区"},{"code":"130973","name":"沧州渤海新区"},{"code":"130981","name":"泊头市"},{"code":"130982","name":"任丘市"},{"code":"130983","name":"黄骅市"},{"code":"130984","name":"河间市"}]},{"code":"1310","name":"廊坊市","children":[{"code":"131002","name":"安次区"},{"code":"131003","name":"广阳区"},{"code":"131022","name":"固安县"},{"code":"131023","name":"永清县"},{"code":"131024","name":"香河县"},{"code":"131025","name":"大城县"},{"code":"131026","name":"文安县"},{"code":"131028","name":"大厂回族自治县"},{"code":"131071","name":"廊坊经济技术开发区"},{"code":"131081","name":"霸州市"},{"code":"131082","name":"三河市"}]},{"code":"1311","name":"衡水市","children":[{"code":"131102","name":"桃城区"},{"code":"131103","name":"冀州区"},{"code":"131121","name":"枣强县"},{"code":"131122","name":"武邑县"},{"code":"131123","name":"武强县"},{"code":"131124","name":"饶阳县"},{"code":"131125","name":"安平县"},{"code":"131126","name":"故城县"},{"code":"131127","name":"景县"},{"code":"131128","name":"阜城县"},{"code":"131171","name":"河北衡水高新技术产业开发区"},{"code":"131172","name":"衡水滨湖新区"},{"code":"131182","name":"深州市"}]}]},{"code":"14","name":"山西省","children":[{"code":"1401","name":"太原市","children":[{"code":"140105","name":"小店区"},{"code":"140106","name":"迎泽区"},{"code":"140107","name":"杏花岭区"},{"code":"140108","name":"尖草坪区"},{"code":"140109","name":"万柏林区"},{"code":"140110","name":"晋源区"},{"code":"140121","name":"清徐县"},{"code":"140122","name":"阳曲县"},{"code":"140123","name":"娄烦县"},{"code":"140171","name":"山西转型综合改革示范区"},{"code":"140181","name":"古交市"}]},{"code":"1402","name":"大同市","children":[{"code":"140212","name":"新荣区"},{"code":"140213","name":"平城区"},{"code":"140214","name":"云冈区"},{"code":"140215","name":"云州区"},{"code":"140221","name":"阳高县"},{"code":"140222","name":"天镇县"},{"code":"140223","name":"广灵县"},{"code":"140224","name":"灵丘县"},{"code":"140225","name":"浑源县"},{"code":"140226","name":"左云县"},{"code":"140271","name":"山西大同经济开发区"}]},{"code":"1403","name":"阳泉市","children":[{"code":"140302","name":"城区"},{"code":"140303","name":"矿区"},{"code":"140311","name":"郊区"},{"code":"140321","name":"平定县"},{"code":"140322","name":"盂县"}]},{"code":"1404","name":"长治市","children":[{"code":"140403","name":"潞州区"},{"code":"140404","name":"上党区"},{"code":"140405","name":"屯留区"},{"code":"140406","name":"潞城区"},{"code":"140423","name":"襄垣县"},{"code":"140425","name":"平顺县"},{"code":"140426","name":"黎城县"},{"code":"140427","name":"壶关县"},{"code":"140428","name":"长子县"},{"code":"140429","name":"武乡县"},{"code":"140430","name":"沁县"},{"code":"140431","name":"沁源县"},{"code":"140471","name":"山西长治高新技术产业园区"}]},{"code":"1405","name":"晋城市","children":[{"code":"140502","name":"城区"},{"code":"140521","name":"沁水县"},{"code":"140522","name":"阳城县"},{"code":"140524","name":"陵川县"},{"code":"140525","name":"泽州县"},{"code":"140581","name":"高平市"}]},{"code":"1406","name":"朔州市","children":[{"code":"140602","name":"朔城区"},{"code":"140603","name":"平鲁区"},{"code":"140621","name":"山阴县"},{"code":"140622","name":"应县"},{"code":"140623","name":"右玉县"},{"code":"140671","name":"山西朔州经济开发区"},{"code":"140681","name":"怀仁市"}]},{"code":"1407","name":"晋中市","children":[{"code":"140702","name":"榆次区"},{"code":"140703","name":"太谷区"},{"code":"140721","name":"榆社县"},{"code":"140722","name":"左权县"},{"code":"140723","name":"和顺县"},{"code":"140724","name":"昔阳县"},{"code":"140725","name":"寿阳县"},{"code":"140727","name":"祁县"},{"code":"140728","name":"平遥县"},{"code":"140729","name":"灵石县"},{"code":"140781","name":"介休市"}]},{"code":"1408","name":"运城市","children":[{"code":"140802","name":"盐湖区"},{"code":"140821","name":"临猗县"},{"code":"140822","name":"万荣县"},{"code":"140823","name":"闻喜县"},{"code":"140824","name":"稷山县"},{"code":"140825","name":"新绛县"},{"code":"140826","name":"绛县"},{"code":"140827","name":"垣曲县"},{"code":"140828","name":"夏县"},{"code":"140829","name":"平陆县"},{"code":"140830","name":"芮城县"},{"code":"140881","name":"永济市"},{"code":"140882","name":"河津市"}]},{"code":"1409","name":"忻州市","children":[{"code":"140902","name":"忻府区"},{"code":"140921","name":"定襄县"},{"code":"140922","name":"五台县"},{"code":"140923","name":"代县"},{"code":"140924","name":"繁峙县"},{"code":"140925","name":"宁武县"},{"code":"140926","name":"静乐县"},{"code":"140927","name":"神池县"},{"code":"140928","name":"五寨县"},{"code":"140929","name":"岢岚县"},{"code":"140930","name":"河曲县"},{"code":"140931","name":"保德县"},{"code":"140932","name":"偏关县"},{"code":"140971","name":"五台山风景名胜区"},{"code":"140981","name":"原平市"}]},{"code":"1410","name":"临汾市","children":[{"code":"141002","name":"尧都区"},{"code":"141021","name":"曲沃县"},{"code":"141022","name":"翼城县"},{"code":"141023","name":"襄汾县"},{"code":"141024","name":"洪洞县"},{"code":"141025","name":"古县"},{"code":"141026","name":"安泽县"},{"code":"141027","name":"浮山县"},{"code":"141028","name":"吉县"},{"code":"141029","name":"乡宁县"},{"code":"141030","name":"大宁县"},{"code":"141031","name":"隰县"},{"code":"141032","name":"永和县"},{"code":"141033","name":"蒲县"},{"code":"141034","name":"汾西县"},{"code":"141081","name":"侯马市"},{"code":"141082","name":"霍州市"}]},{"code":"1411","name":"吕梁市","children":[{"code":"141102","name":"离石区"},{"code":"141121","name":"文水县"},{"code":"141122","name":"交城县"},{"code":"141123","name":"兴县"},{"code":"141124","name":"临县"},{"code":"141125","name":"柳林县"},{"code":"141126","name":"石楼县"},{"code":"141127","name":"岚县"},{"code":"141128","name":"方山县"},{"code":"141129","name":"中阳县"},{"code":"141130","name":"交口县"},{"code":"141181","name":"孝义市"},{"code":"141182","name":"汾阳市"}]}]},{"code":"15","name":"内蒙古自治区","children":[{"code":"1501","name":"呼和浩特市","children":[{"code":"150102","name":"新城区"},{"code":"150103","name":"回民区"},{"code":"150104","name":"玉泉区"},{"code":"150105","name":"赛罕区"},{"code":"150121","name":"土默特左旗"},{"code":"150122","name":"托克托县"},{"code":"150123","name":"和林格尔县"},{"code":"150124","name":"清水河县"},{"code":"150125","name":"武川县"},{"code":"150172","name":"呼和浩特经济技术开发区"}]},{"code":"1502","name":"包头市","children":[{"code":"150202","name":"东河区"},{"code":"150203","name":"昆都仑区"},{"code":"150204","name":"青山区"},{"code":"150205","name":"石拐区"},{"code":"150206","name":"白云鄂博矿区"},{"code":"150207","name":"九原区"},{"code":"150221","name":"土默特右旗"},{"code":"150222","name":"固阳县"},{"code":"150223","name":"达尔罕茂明安联合旗"},{"code":"150271","name":"包头稀土高新技术产业开发区"}]},{"code":"1503","name":"乌海市","children":[{"code":"150302","name":"海勃湾区"},{"code":"150303","name":"海南区"},{"code":"150304","name":"乌达区"}]},{"code":"1504","name":"赤峰市","children":[{"code":"150402","name":"红山区"},{"code":"150403","name":"元宝山区"},{"code":"150404","name":"松山区"},{"code":"150421","name":"阿鲁科尔沁旗"},{"code":"150422","name":"巴林左旗"},{"code":"150423","name":"巴林右旗"},{"code":"150424","name":"林西县"},{"code":"150425","name":"克什克腾旗"},{"code":"150426","name":"翁牛特旗"},{"code":"150428","name":"喀喇沁旗"},{"code":"150429","name":"宁城县"},{"code":"150430","name":"敖汉旗"}]},{"code":"1505","name":"通辽市","children":[{"code":"150502","name":"科尔沁区"},{"code":"150521","name":"科尔沁左翼中旗"},{"code":"150522","name":"科尔沁左翼后旗"},{"code":"150523","name":"开鲁县"},{"code":"150524","name":"库伦旗"},{"code":"150525","name":"奈曼旗"},{"code":"150526","name":"扎鲁特旗"},{"code":"150571","name":"通辽经济技术开发区"},{"code":"150581","name":"霍林郭勒市"}]},{"code":"1506","name":"鄂尔多斯市","children":[{"code":"150602","name":"东胜区"},{"code":"150603","name":"康巴什区"},{"code":"150621","name":"达拉特旗"},{"code":"150622","name":"准格尔旗"},{"code":"150623","name":"鄂托克前旗"},{"code":"150624","name":"鄂托克旗"},{"code":"150625","name":"杭锦旗"},{"code":"150626","name":"乌审旗"},{"code":"150627","name":"伊金霍洛旗"}]},{"code":"1507","name":"呼伦贝尔市","children":[{"code":"150702","name":"海拉尔区"},{"code":"150703","name":"扎赉诺尔区"},{"code":"150721","name":"阿荣旗"},{"code":"150722","name":"莫力达瓦达斡尔族自治旗"},{"code":"150723","name":"鄂伦春自治旗"},{"code":"150724","name":"鄂温克族自治旗"},{"code":"150725","name":"陈巴尔虎旗"},{"code":"150726","name":"新巴尔虎左旗"},{"code":"150727","name":"新巴尔虎右旗"},{"code":"150781","name":"满洲里市"},{"code":"150782","name":"牙克石市"},{"code":"150783","name":"扎兰屯市"},{"code":"150784","name":"额尔古纳市"},{"code":"150785","name":"根河市"}]},{"code":"1508","name":"巴彦淖尔市","children":[{"code":"150802","name":"临河区"},{"code":"150821","name":"五原县"},{"code":"150822","name":"磴口县"},{"code":"150823","name":"乌拉特前旗"},{"code":"150824","name":"乌拉特中旗"},{"code":"150825","name":"乌拉特后旗"},{"code":"150826","name":"杭锦后旗"}]},{"code":"1509","name":"乌兰察布市","children":[{"code":"150902","name":"集宁区"},{"code":"150921","name":"卓资县"},{"code":"150922","name":"化德县"},{"code":"150923","name":"商都县"},{"code":"150924","name":"兴和县"},{"code":"150925","name":"凉城县"},{"code":"150926","name":"察哈尔右翼前旗"},{"code":"150927","name":"察哈尔右翼中旗"},{"code":"150928","name":"察哈尔右翼后旗"},{"code":"150929","name":"四子王旗"},{"code":"150981","name":"丰镇市"}]},{"code":"1522","name":"兴安盟","children":[{"code":"152201","name":"乌兰浩特市"},{"code":"152202","name":"阿尔山市"},{"code":"152221","name":"科尔沁右翼前旗"},{"code":"152222","name":"科尔沁右翼中旗"},{"code":"152223","name":"扎赉特旗"},{"code":"152224","name":"突泉县"}]},{"code":"1525","name":"锡林郭勒盟","children":[{"code":"152501","name":"二连浩特市"},{"code":"152502","name":"锡林浩特市"},{"code":"152522","name":"阿巴嘎旗"},{"code":"152523","name":"苏尼特左旗"},{"code":"152524","name":"苏尼特右旗"},{"code":"152525","name":"东乌珠穆沁旗"},{"code":"152526","name":"西乌珠穆沁旗"},{"code":"152527","name":"太仆寺旗"},{"code":"152528","name":"镶黄旗"},{"code":"152529","name":"正镶白旗"},{"code":"152530","name":"正蓝旗"},{"code":"152531","name":"多伦县"},{"code":"152571","name":"乌拉盖管委会"}]},{"code":"1529","name":"阿拉善盟","children":[{"code":"152921","name":"阿拉善左旗"},{"code":"152922","name":"阿拉善右旗"},{"code":"152923","name":"额济纳旗"},{"code":"152971","name":"内蒙古阿拉善经济开发区"}]}]},{"code":"21","name":"辽宁省","children":[{"code":"2101","name":"沈阳市","children":[{"code":"210102","name":"和平区"},{"code":"210103","name":"沈河区"},{"code":"210104","name":"大东区"},{"code":"210105","name":"皇姑区"},{"code":"210106","name":"铁西区"},{"code":"210111","name":"苏家屯区"},{"code":"210112","name":"浑南区"},{"code":"210113","name":"沈北新区"},{"code":"210114","name":"于洪区"},{"code":"210115","name":"辽中区"},{"code":"210123","name":"康平县"},{"code":"210124","name":"法库县"},{"code":"210181","name":"新民市"}]},{"code":"2102","name":"大连市","children":[{"code":"210202","name":"中山区"},{"code":"210203","name":"西岗区"},{"code":"210204","name":"沙河口区"},{"code":"210211","name":"甘井子区"},{"code":"210212","name":"旅顺口区"},{"code":"210213","name":"金州区"},{"code":"210214","name":"普兰店区"},{"code":"210224","name":"长海县"},{"code":"210281","name":"瓦房店市"},{"code":"210283","name":"庄河市"}]},{"code":"2103","name":"鞍山市","children":[{"code":"210302","name":"铁东区"},{"code":"210303","name":"铁西区"},{"code":"210304","name":"立山区"},{"code":"210311","name":"千山区"},{"code":"210321","name":"台安县"},{"code":"210323","name":"岫岩满族自治县"},{"code":"210381","name":"海城市"}]},{"code":"2104","name":"抚顺市","children":[{"code":"210402","name":"新抚区"},{"code":"210403","name":"东洲区"},{"code":"210404","name":"望花区"},{"code":"210411","name":"顺城区"},{"code":"210421","name":"抚顺县"},{"code":"210422","name":"新宾满族自治县"},{"code":"210423","name":"清原满族自治县"}]},{"code":"2105","name":"本溪市","children":[{"code":"210502","name":"平山区"},{"code":"210503","name":"溪湖区"},{"code":"210504","name":"明山区"},{"code":"210505","name":"南芬区"},{"code":"210521","name":"本溪满族自治县"},{"code":"210522","name":"桓仁满族自治县"}]},{"code":"2106","name":"丹东市","children":[{"code":"210602","name":"元宝区"},{"code":"210603","name":"振兴区"},{"code":"210604","name":"振安区"},{"code":"210624","name":"宽甸满族自治县"},{"code":"210681","name":"东港市"},{"code":"210682","name":"凤城市"}]},{"code":"2107","name":"锦州市","children":[{"code":"210702","name":"古塔区"},{"code":"210703","name":"凌河区"},{"code":"210711","name":"太和区"},{"code":"210726","name":"黑山县"},{"code":"210727","name":"义县"},{"code":"210781","name":"凌海市"},{"code":"210782","name":"北镇市"}]},{"code":"2108","name":"营口市","children":[{"code":"210802","name":"站前区"},{"code":"210803","name":"西市区"},{"code":"210804","name":"鲅鱼圈区"},{"code":"210811","name":"老边区"},{"code":"210881","name":"盖州市"},{"code":"210882","name":"大石桥市"}]},{"code":"2109","name":"阜新市","children":[{"code":"210902","name":"海州区"},{"code":"210903","name":"新邱区"},{"code":"210904","name":"太平区"},{"code":"210905","name":"清河门区"},{"code":"210911","name":"细河区"},{"code":"210921","name":"阜新蒙古族自治县"},{"code":"210922","name":"彰武县"}]},{"code":"2110","name":"辽阳市","children":[{"code":"211002","name":"白塔区"},{"code":"211003","name":"文圣区"},{"code":"211004","name":"宏伟区"},{"code":"211005","name":"弓长岭区"},{"code":"211011","name":"太子河区"},{"code":"211021","name":"辽阳县"},{"code":"211081","name":"灯塔市"}]},{"code":"2111","name":"盘锦市","children":[{"code":"211102","name":"双台子区"},{"code":"211103","name":"兴隆台区"},{"code":"211104","name":"大洼区"},{"code":"211122","name":"盘山县"}]},{"code":"2112","name":"铁岭市","children":[{"code":"211202","name":"银州区"},{"code":"211204","name":"清河区"},{"code":"211221","name":"铁岭县"},{"code":"211223","name":"西丰县"},{"code":"211224","name":"昌图县"},{"code":"211281","name":"调兵山市"},{"code":"211282","name":"开原市"}]},{"code":"2113","name":"朝阳市","children":[{"code":"211302","name":"双塔区"},{"code":"211303","name":"龙城区"},{"code":"211321","name":"朝阳县"},{"code":"211322","name":"建平县"},{"code":"211324","name":"喀喇沁左翼蒙古族自治县"},{"code":"211381","name":"北票市"},{"code":"211382","name":"凌源市"}]},{"code":"2114","name":"葫芦岛市","children":[{"code":"211402","name":"连山区"},{"code":"211403","name":"龙港区"},{"code":"211404","name":"南票区"},{"code":"211421","name":"绥中县"},{"code":"211422","name":"建昌县"},{"code":"211481","name":"兴城市"}]}]},{"code":"22","name":"吉林省","children":[{"code":"2201","name":"长春市","children":[{"code":"220102","name":"南关区"},{"code":"220103","name":"宽城区"},{"code":"220104","name":"朝阳区"},{"code":"220105","name":"二道区"},{"code":"220106","name":"绿园区"},{"code":"220112","name":"双阳区"},{"code":"220113","name":"九台区"},{"code":"220122","name":"农安县"},{"code":"220171","name":"长春经济技术开发区"},{"code":"220172","name":"长春净月高新技术产业开发区"},{"code":"220173","name":"长春高新技术产业开发区"},{"code":"220174","name":"长春汽车经济技术开发区"},{"code":"220182","name":"榆树市"},{"code":"220183","name":"德惠市"},{"code":"220184","name":"公主岭市"}]},{"code":"2202","name":"吉林市","children":[{"code":"220202","name":"昌邑区"},{"code":"220203","name":"龙潭区"},{"code":"220204","name":"船营区"},{"code":"220211","name":"丰满区"},{"code":"220221","name":"永吉县"},{"code":"220271","name":"吉林经济开发区"},{"code":"220272","name":"吉林高新技术产业开发区"},{"code":"220273","name":"吉林中国新加坡食品区"},{"code":"220281","name":"蛟河市"},{"code":"220282","name":"桦甸市"},{"code":"220283","name":"舒兰市"},{"code":"220284","name":"磐石市"}]},{"code":"2203","name":"四平市","children":[{"code":"220302","name":"铁西区"},{"code":"220303","name":"铁东区"},{"code":"220322","name":"梨树县"},{"code":"220323","name":"伊通满族自治县"},{"code":"220382","name":"双辽市"}]},{"code":"2204","name":"辽源市","children":[{"code":"220402","name":"龙山区"},{"code":"220403","name":"西安区"},{"code":"220421","name":"东丰县"},{"code":"220422","name":"东辽县"}]},{"code":"2205","name":"通化市","children":[{"code":"220502","name":"东昌区"},{"code":"220503","name":"二道江区"},{"code":"220521","name":"通化县"},{"code":"220523","name":"辉南县"},{"code":"220524","name":"柳河县"},{"code":"220581","name":"梅河口市"},{"code":"220582","name":"集安市"}]},{"code":"2206","name":"白山市","children":[{"code":"220602","name":"浑江区"},{"code":"220605","name":"江源区"},{"code":"220621","name":"抚松县"},{"code":"220622","name":"靖宇县"},{"code":"220623","name":"长白朝鲜族自治县"},{"code":"220681","name":"临江市"}]},{"code":"2207","name":"松原市","children":[{"code":"220702","name":"宁江区"},{"code":"220721","name":"前郭尔罗斯蒙古族自治县"},{"code":"220722","name":"长岭县"},{"code":"220723","name":"乾安县"},{"code":"220771","name":"吉林松原经济开发区"},{"code":"220781","name":"扶余市"}]},{"code":"2208","name":"白城市","children":[{"code":"220802","name":"洮北区"},{"code":"220821","name":"镇赉县"},{"code":"220822","name":"通榆县"},{"code":"220871","name":"吉林白城经济开发区"},{"code":"220881","name":"洮南市"},{"code":"220882","name":"大安市"}]},{"code":"2224","name":"延边朝鲜族自治州","children":[{"code":"222401","name":"延吉市"},{"code":"222402","name":"图们市"},{"code":"222403","name":"敦化市"},{"code":"222404","name":"珲春市"},{"code":"222405","name":"龙井市"},{"code":"222406","name":"和龙市"},{"code":"222424","name":"汪清县"},{"code":"222426","name":"安图县"}]}]},{"code":"23","name":"黑龙江省","children":[{"code":"2301","name":"哈尔滨市","children":[{"code":"230102","name":"道里区"},{"code":"230103","name":"南岗区"},{"code":"230104","name":"道外区"},{"code":"230108","name":"平房区"},{"code":"230109","name":"松北区"},{"code":"230110","name":"香坊区"},{"code":"230111","name":"呼兰区"},{"code":"230112","name":"阿城区"},{"code":"230113","name":"双城区"},{"code":"230123","name":"依兰县"},{"code":"230124","name":"方正县"},{"code":"230125","name":"宾县"},{"code":"230126","name":"巴彦县"},{"code":"230127","name":"木兰县"},{"code":"230128","name":"通河县"},{"code":"230129","name":"延寿县"},{"code":"230183","name":"尚志市"},{"code":"230184","name":"五常市"}]},{"code":"2302","name":"齐齐哈尔市","children":[{"code":"230202","name":"龙沙区"},{"code":"230203","name":"建华区"},{"code":"230204","name":"铁锋区"},{"code":"230205","name":"昂昂溪区"},{"code":"230206","name":"富拉尔基区"},{"code":"230207","name":"碾子山区"},{"code":"230208","name":"梅里斯达斡尔族区"},{"code":"230221","name":"龙江县"},{"code":"230223","name":"依安县"},{"code":"230224","name":"泰来县"},{"code":"230225","name":"甘南县"},{"code":"230227","name":"富裕县"},{"code":"230229","name":"克山县"},{"code":"230230","name":"克东县"},{"code":"230231","name":"拜泉县"},{"code":"230281","name":"讷河市"}]},{"code":"2303","name":"鸡西市","children":[{"code":"230302","name":"鸡冠区"},{"code":"230303","name":"恒山区"},{"code":"230304","name":"滴道区"},{"code":"230305","name":"梨树区"},{"code":"230306","name":"城子河区"},{"code":"230307","name":"麻山区"},{"code":"230321","name":"鸡东县"},{"code":"230381","name":"虎林市"},{"code":"230382","name":"密山市"}]},{"code":"2304","name":"鹤岗市","children":[{"code":"230402","name":"向阳区"},{"code":"230403","name":"工农区"},{"code":"230404","name":"南山区"},{"code":"230405","name":"兴安区"},{"code":"230406","name":"东山区"},{"code":"230407","name":"兴山区"},{"code":"230421","name":"萝北县"},{"code":"230422","name":"绥滨县"}]},{"code":"2305","name":"双鸭山市","children":[{"code":"230502","name":"尖山区"},{"code":"230503","name":"岭东区"},{"code":"230505","name":"四方台区"},{"code":"230506","name":"宝山区"},{"code":"230521","name":"集贤县"},{"code":"230522","name":"友谊县"},{"code":"230523","name":"宝清县"},{"code":"230524","name":"饶河县"}]},{"code":"2306","name":"大庆市","children":[{"code":"230602","name":"萨尔图区"},{"code":"230603","name":"龙凤区"},{"code":"230604","name":"让胡路区"},{"code":"230605","name":"红岗区"},{"code":"230606","name":"大同区"},{"code":"230621","name":"肇州县"},{"code":"230622","name":"肇源县"},{"code":"230623","name":"林甸县"},{"code":"230624","name":"杜尔伯特蒙古族自治县"},{"code":"230671","name":"大庆高新技术产业开发区"}]},{"code":"2307","name":"伊春市","children":[{"code":"230717","name":"伊美区"},{"code":"230718","name":"乌翠区"},{"code":"230719","name":"友好区"},{"code":"230722","name":"嘉荫县"},{"code":"230723","name":"汤旺县"},{"code":"230724","name":"丰林县"},{"code":"230725","name":"大箐山县"},{"code":"230726","name":"南岔县"},{"code":"230751","name":"金林区"},{"code":"230781","name":"铁力市"}]},{"code":"2308","name":"佳木斯市","children":[{"code":"230803","name":"向阳区"},{"code":"230804","name":"前进区"},{"code":"230805","name":"东风区"},{"code":"230811","name":"郊区"},{"code":"230822","name":"桦南县"},{"code":"230826","name":"桦川县"},{"code":"230828","name":"汤原县"},{"code":"230881","name":"同江市"},{"code":"230882","name":"富锦市"},{"code":"230883","name":"抚远市"}]},{"code":"2309","name":"七台河市","children":[{"code":"230902","name":"新兴区"},{"code":"230903","name":"桃山区"},{"code":"230904","name":"茄子河区"},{"code":"230921","name":"勃利县"}]},{"code":"2310","name":"牡丹江市","children":[{"code":"231002","name":"东安区"},{"code":"231003","name":"阳明区"},{"code":"231004","name":"爱民区"},{"code":"231005","name":"西安区"},{"code":"231025","name":"林口县"},{"code":"231071","name":"牡丹江经济技术开发区"},{"code":"231081","name":"绥芬河市"},{"code":"231083","name":"海林市"},{"code":"231084","name":"宁安市"},{"code":"231085","name":"穆棱市"},{"code":"231086","name":"东宁市"}]},{"code":"2311","name":"黑河市","children":[{"code":"231102","name":"爱辉区"},{"code":"231123","name":"逊克县"},{"code":"231124","name":"孙吴县"},{"code":"231181","name":"北安市"},{"code":"231182","name":"五大连池市"},{"code":"231183","name":"嫩江市"}]},{"code":"2312","name":"绥化市","children":[{"code":"231202","name":"北林区"},{"code":"231221","name":"望奎县"},{"code":"231222","name":"兰西县"},{"code":"231223","name":"青冈县"},{"code":"231224","name":"庆安县"},{"code":"231225","name":"明水县"},{"code":"231226","name":"绥棱县"},{"code":"231281","name":"安达市"},{"code":"231282","name":"肇东市"},{"code":"231283","name":"海伦市"}]},{"code":"2327","name":"大兴安岭地区","children":[{"code":"232701","name":"漠河市"},{"code":"232721","name":"呼玛县"},{"code":"232722","name":"塔河县"},{"code":"232761","name":"加格达奇区"},{"code":"232762","name":"松岭区"},{"code":"232763","name":"新林区"},{"code":"232764","name":"呼中区"}]}]},{"code":"31","name":"上海市","children":[{"code":"3101","name":"市辖区","children":[{"code":"310101","name":"黄浦区"},{"code":"310104","name":"徐汇区"},{"code":"310105","name":"长宁区"},{"code":"310106","name":"静安区"},{"code":"310107","name":"普陀区"},{"code":"310109","name":"虹口区"},{"code":"310110","name":"杨浦区"},{"code":"310112","name":"闵行区"},{"code":"310113","name":"宝山区"},{"code":"310114","name":"嘉定区"},{"code":"310115","name":"浦东新区"},{"code":"310116","name":"金山区"},{"code":"310117","name":"松江区"},{"code":"310118","name":"青浦区"},{"code":"310120","name":"奉贤区"},{"code":"310151","name":"崇明区"}]}]},{"code":"32","name":"江苏省","children":[{"code":"3201","name":"南京市","children":[{"code":"320102","name":"玄武区"},{"code":"320104","name":"秦淮区"},{"code":"320105","name":"建邺区"},{"code":"320106","name":"鼓楼区"},{"code":"320111","name":"浦口区"},{"code":"320113","name":"栖霞区"},{"code":"320114","name":"雨花台区"},{"code":"320115","name":"江宁区"},{"code":"320116","name":"六合区"},{"code":"320117","name":"溧水区"},{"code":"320118","name":"高淳区"}]},{"code":"3202","name":"无锡市","children":[{"code":"320205","name":"锡山区"},{"code":"320206","name":"惠山区"},{"code":"320211","name":"滨湖区"},{"code":"320213","name":"梁溪区"},{"code":"320214","name":"新吴区"},{"code":"320281","name":"江阴市"},{"code":"320282","name":"宜兴市"}]},{"code":"3203","name":"徐州市","children":[{"code":"320302","name":"鼓楼区"},{"code":"320303","name":"云龙区"},{"code":"320305","name":"贾汪区"},{"code":"320311","name":"泉山区"},{"code":"320312","name":"铜山区"},{"code":"320321","name":"丰县"},{"code":"320322","name":"沛县"},{"code":"320324","name":"睢宁县"},{"code":"320371","name":"徐州经济技术开发区"},{"code":"320381","name":"新沂市"},{"code":"320382","name":"邳州市"}]},{"code":"3204","name":"常州市","children":[{"code":"320402","name":"天宁区"},{"code":"320404","name":"钟楼区"},{"code":"320411","name":"新北区"},{"code":"320412","name":"武进区"},{"code":"320413","name":"金坛区"},{"code":"320481","name":"溧阳市"}]},{"code":"3205","name":"苏州市","children":[{"code":"320505","name":"虎丘区"},{"code":"320506","name":"吴中区"},{"code":"320507","name":"相城区"},{"code":"320508","name":"姑苏区"},{"code":"320509","name":"吴江区"},{"code":"320571","name":"苏州工业园区"},{"code":"320581","name":"常熟市"},{"code":"320582","name":"张家港市"},{"code":"320583","name":"昆山市"},{"code":"320585","name":"太仓市"}]},{"code":"3206","name":"南通市","children":[{"code":"320602","name":"崇川区"},{"code":"320611","name":"港闸区"},{"code":"320612","name":"通州区"},{"code":"320623","name":"如东县"},{"code":"320671","name":"南通经济技术开发区"},{"code":"320681","name":"启东市"},{"code":"320682","name":"如皋市"},{"code":"320684","name":"海门市"},{"code":"320685","name":"海安市"}]},{"code":"3207","name":"连云港市","children":[{"code":"320703","name":"连云区"},{"code":"320706","name":"海州区"},{"code":"320707","name":"赣榆区"},{"code":"320722","name":"东海县"},{"code":"320723","name":"灌云县"},{"code":"320724","name":"灌南县"},{"code":"320771","name":"连云港经济技术开发区"},{"code":"320772","name":"连云港高新技术产业开发区"}]},{"code":"3208","name":"淮安市","children":[{"code":"320803","name":"淮安区"},{"code":"320804","name":"淮阴区"},{"code":"320812","name":"清江浦区"},{"code":"320813","name":"洪泽区"},{"code":"320826","name":"涟水县"},{"code":"320830","name":"盱眙县"},{"code":"320831","name":"金湖县"},{"code":"320871","name":"淮安经济技术开发区"}]},{"code":"3209","name":"盐城市","children":[{"code":"320902","name":"亭湖区"},{"code":"320903","name":"盐都区"},{"code":"320904","name":"大丰区"},{"code":"320921","name":"响水县"},{"code":"320922","name":"滨海县"},{"code":"320923","name":"阜宁县"},{"code":"320924","name":"射阳县"},{"code":"320925","name":"建湖县"},{"code":"320971","name":"盐城经济技术开发区"},{"code":"320981","name":"东台市"}]},{"code":"3210","name":"扬州市","children":[{"code":"321002","name":"广陵区"},{"code":"321003","name":"邗江区"},{"code":"321012","name":"江都区"},{"code":"321023","name":"宝应县"},{"code":"321071","name":"扬州经济技术开发区"},{"code":"321081","name":"仪征市"},{"code":"321084","name":"高邮市"}]},{"code":"3211","name":"镇江市","children":[{"code":"321102","name":"京口区"},{"code":"321111","name":"润州区"},{"code":"321112","name":"丹徒区"},{"code":"321171","name":"镇江新区"},{"code":"321181","name":"丹阳市"},{"code":"321182","name":"扬中市"},{"code":"321183","name":"句容市"}]},{"code":"3212","name":"泰州市","children":[{"code":"321202","name":"海陵区"},{"code":"321203","name":"高港区"},{"code":"321204","name":"姜堰区"},{"code":"321271","name":"泰州医药高新技术产业开发区"},{"code":"321281","name":"兴化市"},{"code":"321282","name":"靖江市"},{"code":"321283","name":"泰兴市"}]},{"code":"3213","name":"宿迁市","children":[{"code":"321302","name":"宿城区"},{"code":"321311","name":"宿豫区"},{"code":"321322","name":"沭阳县"},{"code":"321323","name":"泗阳县"},{"code":"321324","name":"泗洪县"},{"code":"321371","name":"宿迁经济技术开发区"}]}]},{"code":"33","name":"浙江省","children":[{"code":"3301","name":"杭州市","children":[{"code":"330102","name":"上城区"},{"code":"330103","name":"下城区"},{"code":"330104","name":"江干区"},{"code":"330105","name":"拱墅区"},{"code":"330106","name":"西湖区"},{"code":"330108","name":"滨江区"},{"code":"330109","name":"萧山区"},{"code":"330110","name":"余杭区"},{"code":"330111","name":"富阳区"},{"code":"330112","name":"临安区"},{"code":"330122","name":"桐庐县"},{"code":"330127","name":"淳安县"},{"code":"330182","name":"建德市"}]},{"code":"3302","name":"宁波市","children":[{"code":"330203","name":"海曙区"},{"code":"330205","name":"江北区"},{"code":"330206","name":"北仑区"},{"code":"330211","name":"镇海区"},{"code":"330212","name":"鄞州区"},{"code":"330213","name":"奉化区"},{"code":"330225","name":"象山县"},{"code":"330226","name":"宁海县"},{"code":"330281","name":"余姚市"},{"code":"330282","name":"慈溪市"}]},{"code":"3303","name":"温州市","children":[{"code":"330302","name":"鹿城区"},{"code":"330303","name":"龙湾区"},{"code":"330304","name":"瓯海区"},{"code":"330305","name":"洞头区"},{"code":"330324","name":"永嘉县"},{"code":"330326","name":"平阳县"},{"code":"330327","name":"苍南县"},{"code":"330328","name":"文成县"},{"code":"330329","name":"泰顺县"},{"code":"330371","name":"温州经济技术开发区"},{"code":"330381","name":"瑞安市"},{"code":"330382","name":"乐清市"},{"code":"330383","name":"龙港市"}]},{"code":"3304","name":"嘉兴市","children":[{"code":"330402","name":"南湖区"},{"code":"330411","name":"秀洲区"},{"code":"330421","name":"嘉善县"},{"code":"330424","name":"海盐县"},{"code":"330481","name":"海宁市"},{"code":"330482","name":"平湖市"},{"code":"330483","name":"桐乡市"}]},{"code":"3305","name":"湖州市","children":[{"code":"330502","name":"吴兴区"},{"code":"330503","name":"南浔区"},{"code":"330521","name":"德清县"},{"code":"330522","name":"长兴县"},{"code":"330523","name":"安吉县"}]},{"code":"3306","name":"绍兴市","children":[{"code":"330602","name":"越城区"},{"code":"330603","name":"柯桥区"},{"code":"330604","name":"上虞区"},{"code":"330624","name":"新昌县"},{"code":"330681","name":"诸暨市"},{"code":"330683","name":"嵊州市"}]},{"code":"3307","name":"金华市","children":[{"code":"330702","name":"婺城区"},{"code":"330703","name":"金东区"},{"code":"330723","name":"武义县"},{"code":"330726","name":"浦江县"},{"code":"330727","name":"磐安县"},{"code":"330781","name":"兰溪市"},{"code":"330782","name":"义乌市"},{"code":"330783","name":"东阳市"},{"code":"330784","name":"永康市"}]},{"code":"3308","name":"衢州市","children":[{"code":"330802","name":"柯城区"},{"code":"330803","name":"衢江区"},{"code":"330822","name":"常山县"},{"code":"330824","name":"开化县"},{"code":"330825","name":"龙游县"},{"code":"330881","name":"江山市"}]},{"code":"3309","name":"舟山市","children":[{"code":"330902","name":"定海区"},{"code":"330903","name":"普陀区"},{"code":"330921","name":"岱山县"},{"code":"330922","name":"嵊泗县"}]},{"code":"3310","name":"台州市","children":[{"code":"331002","name":"椒江区"},{"code":"331003","name":"黄岩区"},{"code":"331004","name":"路桥区"},{"code":"331022","name":"三门县"},{"code":"331023","name":"天台县"},{"code":"331024","name":"仙居县"},{"code":"331081","name":"温岭市"},{"code":"331082","name":"临海市"},{"code":"331083","name":"玉环市"}]},{"code":"3311","name":"丽水市","children":[{"code":"331102","name":"莲都区"},{"code":"331121","name":"青田县"},{"code":"331122","name":"缙云县"},{"code":"331123","name":"遂昌县"},{"code":"331124","name":"松阳县"},{"code":"331125","name":"云和县"},{"code":"331126","name":"庆元县"},{"code":"331127","name":"景宁畲族自治县"},{"code":"331181","name":"龙泉市"}]}]},{"code":"34","name":"安徽省","children":[{"code":"3401","name":"合肥市","children":[{"code":"340102","name":"瑶海区"},{"code":"340103","name":"庐阳区"},{"code":"340104","name":"蜀山区"},{"code":"340111","name":"包河区"},{"code":"340121","name":"长丰县"},{"code":"340122","name":"肥东县"},{"code":"340123","name":"肥西县"},{"code":"340124","name":"庐江县"},{"code":"340171","name":"合肥高新技术产业开发区"},{"code":"340172","name":"合肥经济技术开发区"},{"code":"340173","name":"合肥新站高新技术产业开发区"},{"code":"340181","name":"巢湖市"}]},{"code":"3402","name":"芜湖市","children":[{"code":"340202","name":"镜湖区"},{"code":"340203","name":"弋江区"},{"code":"340207","name":"鸠江区"},{"code":"340208","name":"三山区"},{"code":"340221","name":"芜湖县"},{"code":"340222","name":"繁昌县"},{"code":"340223","name":"南陵县"},{"code":"340271","name":"芜湖经济技术开发区"},{"code":"340272","name":"安徽芜湖长江大桥经济开发区"},{"code":"340281","name":"无为市"}]},{"code":"3403","name":"蚌埠市","children":[{"code":"340302","name":"龙子湖区"},{"code":"340303","name":"蚌山区"},{"code":"340304","name":"禹会区"},{"code":"340311","name":"淮上区"},{"code":"340321","name":"怀远县"},{"code":"340322","name":"五河县"},{"code":"340323","name":"固镇县"},{"code":"340371","name":"蚌埠市高新技术开发区"},{"code":"340372","name":"蚌埠市经济开发区"}]},{"code":"3404","name":"淮南市","children":[{"code":"340402","name":"大通区"},{"code":"340403","name":"田家庵区"},{"code":"340404","name":"谢家集区"},{"code":"340405","name":"八公山区"},{"code":"340406","name":"潘集区"},{"code":"340421","name":"凤台县"},{"code":"340422","name":"寿县"}]},{"code":"3405","name":"马鞍山市","children":[{"code":"340503","name":"花山区"},{"code":"340504","name":"雨山区"},{"code":"340506","name":"博望区"},{"code":"340521","name":"当涂县"},{"code":"340522","name":"含山县"},{"code":"340523","name":"和县"}]},{"code":"3406","name":"淮北市","children":[{"code":"340602","name":"杜集区"},{"code":"340603","name":"相山区"},{"code":"340604","name":"烈山区"},{"code":"340621","name":"濉溪县"}]},{"code":"3407","name":"铜陵市","children":[{"code":"340705","name":"铜官区"},{"code":"340706","name":"义安区"},{"code":"340711","name":"郊区"},{"code":"340722","name":"枞阳县"}]},{"code":"3408","name":"安庆市","children":[{"code":"340802","name":"迎江区"},{"code":"340803","name":"大观区"},{"code":"340811","name":"宜秀区"},{"code":"340822","name":"怀宁县"},{"code":"340825","name":"太湖县"},{"code":"340826","name":"宿松县"},{"code":"340827","name":"望江县"},{"code":"340828","name":"岳西县"},{"code":"340871","name":"安徽安庆经济开发区"},{"code":"340881","name":"桐城市"},{"code":"340882","name":"潜山市"}]},{"code":"3410","name":"黄山市","children":[{"code":"341002","name":"屯溪区"},{"code":"341003","name":"黄山区"},{"code":"341004","name":"徽州区"},{"code":"341021","name":"歙县"},{"code":"341022","name":"休宁县"},{"code":"341023","name":"黟县"},{"code":"341024","name":"祁门县"}]},{"code":"3411","name":"滁州市","children":[{"code":"341102","name":"琅琊区"},{"code":"341103","name":"南谯区"},{"code":"341122","name":"来安县"},{"code":"341124","name":"全椒县"},{"code":"341125","name":"定远县"},{"code":"341126","name":"凤阳县"},{"code":"341171","name":"苏滁现代产业园"},{"code":"341172","name":"滁州经济技术开发区"},{"code":"341181","name":"天长市"},{"code":"341182","name":"明光市"}]},{"code":"3412","name":"阜阳市","children":[{"code":"341202","name":"颍州区"},{"code":"341203","name":"颍东区"},{"code":"341204","name":"颍泉区"},{"code":"341221","name":"临泉县"},{"code":"341222","name":"太和县"},{"code":"341225","name":"阜南县"},{"code":"341226","name":"颍上县"},{"code":"341271","name":"阜阳合肥现代产业园区"},{"code":"341272","name":"阜阳经济技术开发区"},{"code":"341282","name":"界首市"}]},{"code":"3413","name":"宿州市","children":[{"code":"341302","name":"埇桥区"},{"code":"341321","name":"砀山县"},{"code":"341322","name":"萧县"},{"code":"341323","name":"灵璧县"},{"code":"341324","name":"泗县"},{"code":"341371","name":"宿州马鞍山现代产业园区"},{"code":"341372","name":"宿州经济技术开发区"}]},{"code":"3415","name":"六安市","children":[{"code":"341502","name":"金安区"},{"code":"341503","name":"裕安区"},{"code":"341504","name":"叶集区"},{"code":"341522","name":"霍邱县"},{"code":"341523","name":"舒城县"},{"code":"341524","name":"金寨县"},{"code":"341525","name":"霍山县"}]},{"code":"3416","name":"亳州市","children":[{"code":"341602","name":"谯城区"},{"code":"341621","name":"涡阳县"},{"code":"341622","name":"蒙城县"},{"code":"341623","name":"利辛县"}]},{"code":"3417","name":"池州市","children":[{"code":"341702","name":"贵池区"},{"code":"341721","name":"东至县"},{"code":"341722","name":"石台县"},{"code":"341723","name":"青阳县"}]},{"code":"3418","name":"宣城市","children":[{"code":"341802","name":"宣州区"},{"code":"341821","name":"郎溪县"},{"code":"341823","name":"泾县"},{"code":"341824","name":"绩溪县"},{"code":"341825","name":"旌德县"},{"code":"341871","name":"宣城市经济开发区"},{"code":"341881","name":"宁国市"},{"code":"341882","name":"广德市"}]}]},{"code":"35","name":"福建省","children":[{"code":"3501","name":"福州市","children":[{"code":"350102","name":"鼓楼区"},{"code":"350103","name":"台江区"},{"code":"350104","name":"仓山区"},{"code":"350105","name":"马尾区"},{"code":"350111","name":"晋安区"},{"code":"350112","name":"长乐区"},{"code":"350121","name":"闽侯县"},{"code":"350122","name":"连江县"},{"code":"350123","name":"罗源县"},{"code":"350124","name":"闽清县"},{"code":"350125","name":"永泰县"},{"code":"350128","name":"平潭县"},{"code":"350181","name":"福清市"}]},{"code":"3502","name":"厦门市","children":[{"code":"350203","name":"思明区"},{"code":"350205","name":"海沧区"},{"code":"350206","name":"湖里区"},{"code":"350211","name":"集美区"},{"code":"350212","name":"同安区"},{"code":"350213","name":"翔安区"}]},{"code":"3503","name":"莆田市","children":[{"code":"350302","name":"城厢区"},{"code":"350303","name":"涵江区"},{"code":"350304","name":"荔城区"},{"code":"350305","name":"秀屿区"},{"code":"350322","name":"仙游县"}]},{"code":"3504","name":"三明市","children":[{"code":"350402","name":"梅列区"},{"code":"350403","name":"三元区"},{"code":"350421","name":"明溪县"},{"code":"350423","name":"清流县"},{"code":"350424","name":"宁化县"},{"code":"350425","name":"大田县"},{"code":"350426","name":"尤溪县"},{"code":"350427","name":"沙县"},{"code":"350428","name":"将乐县"},{"code":"350429","name":"泰宁县"},{"code":"350430","name":"建宁县"},{"code":"350481","name":"永安市"}]},{"code":"3505","name":"泉州市","children":[{"code":"350502","name":"鲤城区"},{"code":"350503","name":"丰泽区"},{"code":"350504","name":"洛江区"},{"code":"350505","name":"泉港区"},{"code":"350521","name":"惠安县"},{"code":"350524","name":"安溪县"},{"code":"350525","name":"永春县"},{"code":"350526","name":"德化县"},{"code":"350527","name":"金门县"},{"code":"350581","name":"石狮市"},{"code":"350582","name":"晋江市"},{"code":"350583","name":"南安市"}]},{"code":"3506","name":"漳州市","children":[{"code":"350602","name":"芗城区"},{"code":"350603","name":"龙文区"},{"code":"350622","name":"云霄县"},{"code":"350623","name":"漳浦县"},{"code":"350624","name":"诏安县"},{"code":"350625","name":"长泰县"},{"code":"350626","name":"东山县"},{"code":"350627","name":"南靖县"},{"code":"350628","name":"平和县"},{"code":"350629","name":"华安县"},{"code":"350681","name":"龙海市"}]},{"code":"3507","name":"南平市","children":[{"code":"350702","name":"延平区"},{"code":"350703","name":"建阳区"},{"code":"350721","name":"顺昌县"},{"code":"350722","name":"浦城县"},{"code":"350723","name":"光泽县"},{"code":"350724","name":"松溪县"},{"code":"350725","name":"政和县"},{"code":"350781","name":"邵武市"},{"code":"350782","name":"武夷山市"},{"code":"350783","name":"建瓯市"}]},{"code":"3508","name":"龙岩市","children":[{"code":"350802","name":"新罗区"},{"code":"350803","name":"永定区"},{"code":"350821","name":"长汀县"},{"code":"350823","name":"上杭县"},{"code":"350824","name":"武平县"},{"code":"350825","name":"连城县"},{"code":"350881","name":"漳平市"}]},{"code":"3509","name":"宁德市","children":[{"code":"350902","name":"蕉城区"},{"code":"350921","name":"霞浦县"},{"code":"350922","name":"古田县"},{"code":"350923","name":"屏南县"},{"code":"350924","name":"寿宁县"},{"code":"350925","name":"周宁县"},{"code":"350926","name":"柘荣县"},{"code":"350981","name":"福安市"},{"code":"350982","name":"福鼎市"}]}]},{"code":"36","name":"江西省","children":[{"code":"3601","name":"南昌市","children":[{"code":"360102","name":"东湖区"},{"code":"360103","name":"西湖区"},{"code":"360104","name":"青云谱区"},{"code":"360111","name":"青山湖区"},{"code":"360112","name":"新建区"},{"code":"360113","name":"红谷滩区"},{"code":"360121","name":"南昌县"},{"code":"360123","name":"安义县"},{"code":"360124","name":"进贤县"}]},{"code":"3602","name":"景德镇市","children":[{"code":"360202","name":"昌江区"},{"code":"360203","name":"珠山区"},{"code":"360222","name":"浮梁县"},{"code":"360281","name":"乐平市"}]},{"code":"3603","name":"萍乡市","children":[{"code":"360302","name":"安源区"},{"code":"360313","name":"湘东区"},{"code":"360321","name":"莲花县"},{"code":"360322","name":"上栗县"},{"code":"360323","name":"芦溪县"}]},{"code":"3604","name":"九江市","children":[{"code":"360402","name":"濂溪区"},{"code":"360403","name":"浔阳区"},{"code":"360404","name":"柴桑区"},{"code":"360423","name":"武宁县"},{"code":"360424","name":"修水县"},{"code":"360425","name":"永修县"},{"code":"360426","name":"德安县"},{"code":"360428","name":"都昌县"},{"code":"360429","name":"湖口县"},{"code":"360430","name":"彭泽县"},{"code":"360481","name":"瑞昌市"},{"code":"360482","name":"共青城市"},{"code":"360483","name":"庐山市"}]},{"code":"3605","name":"新余市","children":[{"code":"360502","name":"渝水区"},{"code":"360521","name":"分宜县"}]},{"code":"3606","name":"鹰潭市","children":[{"code":"360602","name":"月湖区"},{"code":"360603","name":"余江区"},{"code":"360681","name":"贵溪市"}]},{"code":"3607","name":"赣州市","children":[{"code":"360702","name":"章贡区"},{"code":"360703","name":"南康区"},{"code":"360704","name":"赣县区"},{"code":"360722","name":"信丰县"},{"code":"360723","name":"大余县"},{"code":"360724","name":"上犹县"},{"code":"360725","name":"崇义县"},{"code":"360726","name":"安远县"},{"code":"360728","name":"定南县"},{"code":"360729","name":"全南县"},{"code":"360730","name":"宁都县"},{"code":"360731","name":"于都县"},{"code":"360732","name":"兴国县"},{"code":"360733","name":"会昌县"},{"code":"360734","name":"寻乌县"},{"code":"360735","name":"石城县"},{"code":"360781","name":"瑞金市"},{"code":"360783","name":"龙南市"}]},{"code":"3608","name":"吉安市","children":[{"code":"360802","name":"吉州区"},{"code":"360803","name":"青原区"},{"code":"360821","name":"吉安县"},{"code":"360822","name":"吉水县"},{"code":"360823","name":"峡江县"},{"code":"360824","name":"新干县"},{"code":"360825","name":"永丰县"},{"code":"360826","name":"泰和县"},{"code":"360827","name":"遂川县"},{"code":"360828","name":"万安县"},{"code":"360829","name":"安福县"},{"code":"360830","name":"永新县"},{"code":"360881","name":"井冈山市"}]},{"code":"3609","name":"宜春市","children":[{"code":"360902","name":"袁州区"},{"code":"360921","name":"奉新县"},{"code":"360922","name":"万载县"},{"code":"360923","name":"上高县"},{"code":"360924","name":"宜丰县"},{"code":"360925","name":"靖安县"},{"code":"360926","name":"铜鼓县"},{"code":"360981","name":"丰城市"},{"code":"360982","name":"樟树市"},{"code":"360983","name":"高安市"}]},{"code":"3610","name":"抚州市","children":[{"code":"361002","name":"临川区"},{"code":"361003","name":"东乡区"},{"code":"361021","name":"南城县"},{"code":"361022","name":"黎川县"},{"code":"361023","name":"南丰县"},{"code":"361024","name":"崇仁县"},{"code":"361025","name":"乐安县"},{"code":"361026","name":"宜黄县"},{"code":"361027","name":"金溪县"},{"code":"361028","name":"资溪县"},{"code":"361030","name":"广昌县"}]},{"code":"3611","name":"上饶市","children":[{"code":"361102","name":"信州区"},{"code":"361103","name":"广丰区"},{"code":"361104","name":"广信区"},{"code":"361123","name":"玉山县"},{"code":"361124","name":"铅山县"},{"code":"361125","name":"横峰县"},{"code":"361126","name":"弋阳县"},{"code":"361127","name":"余干县"},{"code":"361128","name":"鄱阳县"},{"code":"361129","name":"万年县"},{"code":"361130","name":"婺源县"},{"code":"361181","name":"德兴市"}]}]},{"code":"37","name":"山东省","children":[{"code":"3701","name":"济南市","children":[{"code":"370102","name":"历下区"},{"code":"370103","name":"市中区"},{"code":"370104","name":"槐荫区"},{"code":"370105","name":"天桥区"},{"code":"370112","name":"历城区"},{"code":"370113","name":"长清区"},{"code":"370114","name":"章丘区"},{"code":"370115","name":"济阳区"},{"code":"370116","name":"莱芜区"},{"code":"370117","name":"钢城区"},{"code":"370124","name":"平阴县"},{"code":"370126","name":"商河县"},{"code":"370171","name":"济南高新技术产业开发区"}]},{"code":"3702","name":"青岛市","children":[{"code":"370202","name":"市南区"},{"code":"370203","name":"市北区"},{"code":"370211","name":"黄岛区"},{"code":"370212","name":"崂山区"},{"code":"370213","name":"李沧区"},{"code":"370214","name":"城阳区"},{"code":"370215","name":"即墨区"},{"code":"370271","name":"青岛高新技术产业开发区"},{"code":"370281","name":"胶州市"},{"code":"370283","name":"平度市"},{"code":"370285","name":"莱西市"}]},{"code":"3703","name":"淄博市","children":[{"code":"370302","name":"淄川区"},{"code":"370303","name":"张店区"},{"code":"370304","name":"博山区"},{"code":"370305","name":"临淄区"},{"code":"370306","name":"周村区"},{"code":"370321","name":"桓台县"},{"code":"370322","name":"高青县"},{"code":"370323","name":"沂源县"}]},{"code":"3704","name":"枣庄市","children":[{"code":"370402","name":"市中区"},{"code":"370403","name":"薛城区"},{"code":"370404","name":"峄城区"},{"code":"370405","name":"台儿庄区"},{"code":"370406","name":"山亭区"},{"code":"370481","name":"滕州市"}]},{"code":"3705","name":"东营市","children":[{"code":"370502","name":"东营区"},{"code":"370503","name":"河口区"},{"code":"370505","name":"垦利区"},{"code":"370522","name":"利津县"},{"code":"370523","name":"广饶县"},{"code":"370571","name":"东营经济技术开发区"},{"code":"370572","name":"东营港经济开发区"}]},{"code":"3706","name":"烟台市","children":[{"code":"370602","name":"芝罘区"},{"code":"370611","name":"福山区"},{"code":"370612","name":"牟平区"},{"code":"370613","name":"莱山区"},{"code":"370614","name":"蓬莱区"},{"code":"370671","name":"烟台高新技术产业开发区"},{"code":"370672","name":"烟台经济技术开发区"},{"code":"370681","name":"龙口市"},{"code":"370682","name":"莱阳市"},{"code":"370683","name":"莱州市"},{"code":"370685","name":"招远市"},{"code":"370686","name":"栖霞市"},{"code":"370687","name":"海阳市"}]},{"code":"3707","name":"潍坊市","children":[{"code":"370702","name":"潍城区"},{"code":"370703","name":"寒亭区"},{"code":"370704","name":"坊子区"},{"code":"370705","name":"奎文区"},{"code":"370724","name":"临朐县"},{"code":"370725","name":"昌乐县"},{"code":"370772","name":"潍坊滨海经济技术开发区"},{"code":"370781","name":"青州市"},{"code":"370782","name":"诸城市"},{"code":"370783","name":"寿光市"},{"code":"370784","name":"安丘市"},{"code":"370785","name":"高密市"},{"code":"370786","name":"昌邑市"}]},{"code":"3708","name":"济宁市","children":[{"code":"370811","name":"任城区"},{"code":"370812","name":"兖州区"},{"code":"370826","name":"微山县"},{"code":"370827","name":"鱼台县"},{"code":"370828","name":"金乡县"},{"code":"370829","name":"嘉祥县"},{"code":"370830","name":"汶上县"},{"code":"370831","name":"泗水县"},{"code":"370832","name":"梁山县"},{"code":"370871","name":"济宁高新技术产业开发区"},{"code":"370881","name":"曲阜市"},{"code":"370883","name":"邹城市"}]},{"code":"3709","name":"泰安市","children":[{"code":"370902","name":"泰山区"},{"code":"370911","name":"岱岳区"},{"code":"370921","name":"宁阳县"},{"code":"370923","name":"东平县"},{"code":"370982","name":"新泰市"},{"code":"370983","name":"肥城市"}]},{"code":"3710","name":"威海市","children":[{"code":"371002","name":"环翠区"},{"code":"371003","name":"文登区"},{"code":"371071","name":"威海火炬高技术产业开发区"},{"code":"371072","name":"威海经济技术开发区"},{"code":"371073","name":"威海临港经济技术开发区"},{"code":"371082","name":"荣成市"},{"code":"371083","name":"乳山市"}]},{"code":"3711","name":"日照市","children":[{"code":"371102","name":"东港区"},{"code":"371103","name":"岚山区"},{"code":"371121","name":"五莲县"},{"code":"371122","name":"莒县"},{"code":"371171","name":"日照经济技术开发区"}]},{"code":"3713","name":"临沂市","children":[{"code":"371302","name":"兰山区"},{"code":"371311","name":"罗庄区"},{"code":"371312","name":"河东区"},{"code":"371321","name":"沂南县"},{"code":"371322","name":"郯城县"},{"code":"371323","name":"沂水县"},{"code":"371324","name":"兰陵县"},{"code":"371325","name":"费县"},{"code":"371326","name":"平邑县"},{"code":"371327","name":"莒南县"},{"code":"371328","name":"蒙阴县"},{"code":"371329","name":"临沭县"},{"code":"371371","name":"临沂高新技术产业开发区"}]},{"code":"3714","name":"德州市","children":[{"code":"371402","name":"德城区"},{"code":"371403","name":"陵城区"},{"code":"371422","name":"宁津县"},{"code":"371423","name":"庆云县"},{"code":"371424","name":"临邑县"},{"code":"371425","name":"齐河县"},{"code":"371426","name":"平原县"},{"code":"371427","name":"夏津县"},{"code":"371428","name":"武城县"},{"code":"371471","name":"德州经济技术开发区"},{"code":"371472","name":"德州运河经济开发区"},{"code":"371481","name":"乐陵市"},{"code":"371482","name":"禹城市"}]},{"code":"3715","name":"聊城市","children":[{"code":"371502","name":"东昌府区"},{"code":"371503","name":"茌平区"},{"code":"371521","name":"阳谷县"},{"code":"371522","name":"莘县"},{"code":"371524","name":"东阿县"},{"code":"371525","name":"冠县"},{"code":"371526","name":"高唐县"},{"code":"371581","name":"临清市"}]},{"code":"3716","name":"滨州市","children":[{"code":"371602","name":"滨城区"},{"code":"371603","name":"沾化区"},{"code":"371621","name":"惠民县"},{"code":"371622","name":"阳信县"},{"code":"371623","name":"无棣县"},{"code":"371625","name":"博兴县"},{"code":"371681","name":"邹平市"}]},{"code":"3717","name":"菏泽市","children":[{"code":"371702","name":"牡丹区"},{"code":"371703","name":"定陶区"},{"code":"371721","name":"曹县"},{"code":"371722","name":"单县"},{"code":"371723","name":"成武县"},{"code":"371724","name":"巨野县"},{"code":"371725","name":"郓城县"},{"code":"371726","name":"鄄城县"},{"code":"371728","name":"东明县"},{"code":"371771","name":"菏泽经济技术开发区"},{"code":"371772","name":"菏泽高新技术开发区"}]}]},{"code":"41","name":"河南省","children":[{"code":"4101","name":"郑州市","children":[{"code":"410102","name":"中原区"},{"code":"410103","name":"二七区"},{"code":"410104","name":"管城回族区"},{"code":"410105","name":"金水区"},{"code":"410106","name":"上街区"},{"code":"410108","name":"惠济区"},{"code":"410122","name":"中牟县"},{"code":"410171","name":"郑州经济技术开发区"},{"code":"410172","name":"郑州高新技术产业开发区"},{"code":"410173","name":"郑州航空港经济综合实验区"},{"code":"410181","name":"巩义市"},{"code":"410182","name":"荥阳市"},{"code":"410183","name":"新密市"},{"code":"410184","name":"新郑市"},{"code":"410185","name":"登封市"}]},{"code":"4102","name":"开封市","children":[{"code":"410202","name":"龙亭区"},{"code":"410203","name":"顺河回族区"},{"code":"410204","name":"鼓楼区"},{"code":"410205","name":"禹王台区"},{"code":"410212","name":"祥符区"},{"code":"410221","name":"杞县"},{"code":"410222","name":"通许县"},{"code":"410223","name":"尉氏县"},{"code":"410225","name":"兰考县"}]},{"code":"4103","name":"洛阳市","children":[{"code":"410302","name":"老城区"},{"code":"410303","name":"西工区"},{"code":"410304","name":"瀍河回族区"},{"code":"410305","name":"涧西区"},{"code":"410306","name":"吉利区"},{"code":"410311","name":"洛龙区"},{"code":"410322","name":"孟津县"},{"code":"410323","name":"新安县"},{"code":"410324","name":"栾川县"},{"code":"410325","name":"嵩县"},{"code":"410326","name":"汝阳县"},{"code":"410327","name":"宜阳县"},{"code":"410328","name":"洛宁县"},{"code":"410329","name":"伊川县"},{"code":"410371","name":"洛阳高新技术产业开发区"},{"code":"410381","name":"偃师市"}]},{"code":"4104","name":"平顶山市","children":[{"code":"410402","name":"新华区"},{"code":"410403","name":"卫东区"},{"code":"410404","name":"石龙区"},{"code":"410411","name":"湛河区"},{"code":"410421","name":"宝丰县"},{"code":"410422","name":"叶县"},{"code":"410423","name":"鲁山县"},{"code":"410425","name":"郏县"},{"code":"410471","name":"平顶山高新技术产业开发区"},{"code":"410472","name":"平顶山市城乡一体化示范区"},{"code":"410481","name":"舞钢市"},{"code":"410482","name":"汝州市"}]},{"code":"4105","name":"安阳市","children":[{"code":"410502","name":"文峰区"},{"code":"410503","name":"北关区"},{"code":"410505","name":"殷都区"},{"code":"410506","name":"龙安区"},{"code":"410522","name":"安阳县"},{"code":"410523","name":"汤阴县"},{"code":"410526","name":"滑县"},{"code":"410527","name":"内黄县"},{"code":"410571","name":"安阳高新技术产业开发区"},{"code":"410581","name":"林州市"}]},{"code":"4106","name":"鹤壁市","children":[{"code":"410602","name":"鹤山区"},{"code":"410603","name":"山城区"},{"code":"410611","name":"淇滨区"},{"code":"410621","name":"浚县"},{"code":"410622","name":"淇县"},{"code":"410671","name":"鹤壁经济技术开发区"}]},{"code":"4107","name":"新乡市","children":[{"code":"410702","name":"红旗区"},{"code":"410703","name":"卫滨区"},{"code":"410704","name":"凤泉区"},{"code":"410711","name":"牧野区"},{"code":"410721","name":"新乡县"},{"code":"410724","name":"获嘉县"},{"code":"410725","name":"原阳县"},{"code":"410726","name":"延津县"},{"code":"410727","name":"封丘县"},{"code":"410771","name":"新乡高新技术产业开发区"},{"code":"410772","name":"新乡经济技术开发区"},{"code":"410773","name":"新乡市平原城乡一体化示范区"},{"code":"410781","name":"卫辉市"},{"code":"410782","name":"辉县市"},{"code":"410783","name":"长垣市"}]},{"code":"4108","name":"焦作市","children":[{"code":"410802","name":"解放区"},{"code":"410803","name":"中站区"},{"code":"410804","name":"马村区"},{"code":"410811","name":"山阳区"},{"code":"410821","name":"修武县"},{"code":"410822","name":"博爱县"},{"code":"410823","name":"武陟县"},{"code":"410825","name":"温县"},{"code":"410871","name":"焦作城乡一体化示范区"},{"code":"410882","name":"沁阳市"},{"code":"410883","name":"孟州市"}]},{"code":"4109","name":"濮阳市","children":[{"code":"410902","name":"华龙区"},{"code":"410922","name":"清丰县"},{"code":"410923","name":"南乐县"},{"code":"410926","name":"范县"},{"code":"410927","name":"台前县"},{"code":"410928","name":"濮阳县"},{"code":"410971","name":"河南濮阳工业园区"},{"code":"410972","name":"濮阳经济技术开发区"}]},{"code":"4110","name":"许昌市","children":[{"code":"411002","name":"魏都区"},{"code":"411003","name":"建安区"},{"code":"411024","name":"鄢陵县"},{"code":"411025","name":"襄城县"},{"code":"411071","name":"许昌经济技术开发区"},{"code":"411081","name":"禹州市"},{"code":"411082","name":"长葛市"}]},{"code":"4111","name":"漯河市","children":[{"code":"411102","name":"源汇区"},{"code":"411103","name":"郾城区"},{"code":"411104","name":"召陵区"},{"code":"411121","name":"舞阳县"},{"code":"411122","name":"临颍县"},{"code":"411171","name":"漯河经济技术开发区"}]},{"code":"4112","name":"三门峡市","children":[{"code":"411202","name":"湖滨区"},{"code":"411203","name":"陕州区"},{"code":"411221","name":"渑池县"},{"code":"411224","name":"卢氏县"},{"code":"411271","name":"河南三门峡经济开发区"},{"code":"411281","name":"义马市"},{"code":"411282","name":"灵宝市"}]},{"code":"4113","name":"南阳市","children":[{"code":"411302","name":"宛城区"},{"code":"411303","name":"卧龙区"},{"code":"411321","name":"南召县"},{"code":"411322","name":"方城县"},{"code":"411323","name":"西峡县"},{"code":"411324","name":"镇平县"},{"code":"411325","name":"内乡县"},{"code":"411326","name":"淅川县"},{"code":"411327","name":"社旗县"},{"code":"411328","name":"唐河县"},{"code":"411329","name":"新野县"},{"code":"411330","name":"桐柏县"},{"code":"411371","name":"南阳高新技术产业开发区"},{"code":"411372","name":"南阳市城乡一体化示范区"},{"code":"411381","name":"邓州市"}]},{"code":"4114","name":"商丘市","children":[{"code":"411402","name":"梁园区"},{"code":"411403","name":"睢阳区"},{"code":"411421","name":"民权县"},{"code":"411422","name":"睢县"},{"code":"411423","name":"宁陵县"},{"code":"411424","name":"柘城县"},{"code":"411425","name":"虞城县"},{"code":"411426","name":"夏邑县"},{"code":"411471","name":"豫东综合物流产业聚集区"},{"code":"411472","name":"河南商丘经济开发区"},{"code":"411481","name":"永城市"}]},{"code":"4115","name":"信阳市","children":[{"code":"411502","name":"浉河区"},{"code":"411503","name":"平桥区"},{"code":"411521","name":"罗山县"},{"code":"411522","name":"光山县"},{"code":"411523","name":"新县"},{"code":"411524","name":"商城县"},{"code":"411525","name":"固始县"},{"code":"411526","name":"潢川县"},{"code":"411527","name":"淮滨县"},{"code":"411528","name":"息县"},{"code":"411571","name":"信阳高新技术产业开发区"}]},{"code":"4116","name":"周口市","children":[{"code":"411602","name":"川汇区"},{"code":"411603","name":"淮阳区"},{"code":"411621","name":"扶沟县"},{"code":"411622","name":"西华县"},{"code":"411623","name":"商水县"},{"code":"411624","name":"沈丘县"},{"code":"411625","name":"郸城县"},{"code":"411627","name":"太康县"},{"code":"411628","name":"鹿邑县"},{"code":"411671","name":"河南周口经济开发区"},{"code":"411681","name":"项城市"}]},{"code":"4117","name":"驻马店市","children":[{"code":"411702","name":"驿城区"},{"code":"411721","name":"西平县"},{"code":"411722","name":"上蔡县"},{"code":"411723","name":"平舆县"},{"code":"411724","name":"正阳县"},{"code":"411725","name":"确山县"},{"code":"411726","name":"泌阳县"},{"code":"411727","name":"汝南县"},{"code":"411728","name":"遂平县"},{"code":"411729","name":"新蔡县"},{"code":"411771","name":"河南驻马店经济开发区"}]},{"code":"4190","name":"省直辖县级行政区划","children":[{"code":"419001","name":"济源市"}]}]},{"code":"42","name":"湖北省","children":[{"code":"4201","name":"武汉市","children":[{"code":"420102","name":"江岸区"},{"code":"420103","name":"江汉区"},{"code":"420104","name":"硚口区"},{"code":"420105","name":"汉阳区"},{"code":"420106","name":"武昌区"},{"code":"420107","name":"青山区"},{"code":"420111","name":"洪山区"},{"code":"420112","name":"东西湖区"},{"code":"420113","name":"汉南区"},{"code":"420114","name":"蔡甸区"},{"code":"420115","name":"江夏区"},{"code":"420116","name":"黄陂区"},{"code":"420117","name":"新洲区"}]},{"code":"4202","name":"黄石市","children":[{"code":"420202","name":"黄石港区"},{"code":"420203","name":"西塞山区"},{"code":"420204","name":"下陆区"},{"code":"420205","name":"铁山区"},{"code":"420222","name":"阳新县"},{"code":"420281","name":"大冶市"}]},{"code":"4203","name":"十堰市","children":[{"code":"420302","name":"茅箭区"},{"code":"420303","name":"张湾区"},{"code":"420304","name":"郧阳区"},{"code":"420322","name":"郧西县"},{"code":"420323","name":"竹山县"},{"code":"420324","name":"竹溪县"},{"code":"420325","name":"房县"},{"code":"420381","name":"丹江口市"}]},{"code":"4205","name":"宜昌市","children":[{"code":"420502","name":"西陵区"},{"code":"420503","name":"伍家岗区"},{"code":"420504","name":"点军区"},{"code":"420505","name":"猇亭区"},{"code":"420506","name":"夷陵区"},{"code":"420525","name":"远安县"},{"code":"420526","name":"兴山县"},{"code":"420527","name":"秭归县"},{"code":"420528","name":"长阳土家族自治县"},{"code":"420529","name":"五峰土家族自治县"},{"code":"420581","name":"宜都市"},{"code":"420582","name":"当阳市"},{"code":"420583","name":"枝江市"}]},{"code":"4206","name":"襄阳市","children":[{"code":"420602","name":"襄城区"},{"code":"420606","name":"樊城区"},{"code":"420607","name":"襄州区"},{"code":"420624","name":"南漳县"},{"code":"420625","name":"谷城县"},{"code":"420626","name":"保康县"},{"code":"420682","name":"老河口市"},{"code":"420683","name":"枣阳市"},{"code":"420684","name":"宜城市"}]},{"code":"4207","name":"鄂州市","children":[{"code":"420702","name":"梁子湖区"},{"code":"420703","name":"华容区"},{"code":"420704","name":"鄂城区"}]},{"code":"4208","name":"荆门市","children":[{"code":"420802","name":"东宝区"},{"code":"420804","name":"掇刀区"},{"code":"420822","name":"沙洋县"},{"code":"420881","name":"钟祥市"},{"code":"420882","name":"京山市"}]},{"code":"4209","name":"孝感市","children":[{"code":"420902","name":"孝南区"},{"code":"420921","name":"孝昌县"},{"code":"420922","name":"大悟县"},{"code":"420923","name":"云梦县"},{"code":"420981","name":"应城市"},{"code":"420982","name":"安陆市"},{"code":"420984","name":"汉川市"}]},{"code":"4210","name":"荆州市","children":[{"code":"421002","name":"沙市区"},{"code":"421003","name":"荆州区"},{"code":"421022","name":"公安县"},{"code":"421023","name":"监利县"},{"code":"421024","name":"江陵县"},{"code":"421071","name":"荆州经济技术开发区"},{"code":"421081","name":"石首市"},{"code":"421083","name":"洪湖市"},{"code":"421087","name":"松滋市"}]},{"code":"4211","name":"黄冈市","children":[{"code":"421102","name":"黄州区"},{"code":"421121","name":"团风县"},{"code":"421122","name":"红安县"},{"code":"421123","name":"罗田县"},{"code":"421124","name":"英山县"},{"code":"421125","name":"浠水县"},{"code":"421126","name":"蕲春县"},{"code":"421127","name":"黄梅县"},{"code":"421171","name":"龙感湖管理区"},{"code":"421181","name":"麻城市"},{"code":"421182","name":"武穴市"}]},{"code":"4212","name":"咸宁市","children":[{"code":"421202","name":"咸安区"},{"code":"421221","name":"嘉鱼县"},{"code":"421222","name":"通城县"},{"code":"421223","name":"崇阳县"},{"code":"421224","name":"通山县"},{"code":"421281","name":"赤壁市"}]},{"code":"4213","name":"随州市","children":[{"code":"421303","name":"曾都区"},{"code":"421321","name":"随县"},{"code":"421381","name":"广水市"}]},{"code":"4228","name":"恩施土家族苗族自治州","children":[{"code":"422801","name":"恩施市"},{"code":"422802","name":"利川市"},{"code":"422822","name":"建始县"},{"code":"422823","name":"巴东县"},{"code":"422825","name":"宣恩县"},{"code":"422826","name":"咸丰县"},{"code":"422827","name":"来凤县"},{"code":"422828","name":"鹤峰县"}]},{"code":"4290","name":"省直辖县级行政区划","children":[{"code":"429004","name":"仙桃市"},{"code":"429005","name":"潜江市"},{"code":"429006","name":"天门市"},{"code":"429021","name":"神农架林区"}]}]},{"code":"43","name":"湖南省","children":[{"code":"4301","name":"长沙市","children":[{"code":"430102","name":"芙蓉区"},{"code":"430103","name":"天心区"},{"code":"430104","name":"岳麓区"},{"code":"430105","name":"开福区"},{"code":"430111","name":"雨花区"},{"code":"430112","name":"望城区"},{"code":"430121","name":"长沙县"},{"code":"430181","name":"浏阳市"},{"code":"430182","name":"宁乡市"}]},{"code":"4302","name":"株洲市","children":[{"code":"430202","name":"荷塘区"},{"code":"430203","name":"芦淞区"},{"code":"430204","name":"石峰区"},{"code":"430211","name":"天元区"},{"code":"430212","name":"渌口区"},{"code":"430223","name":"攸县"},{"code":"430224","name":"茶陵县"},{"code":"430225","name":"炎陵县"},{"code":"430271","name":"云龙示范区"},{"code":"430281","name":"醴陵市"}]},{"code":"4303","name":"湘潭市","children":[{"code":"430302","name":"雨湖区"},{"code":"430304","name":"岳塘区"},{"code":"430321","name":"湘潭县"},{"code":"430371","name":"湖南湘潭高新技术产业园区"},{"code":"430372","name":"湘潭昭山示范区"},{"code":"430373","name":"湘潭九华示范区"},{"code":"430381","name":"湘乡市"},{"code":"430382","name":"韶山市"}]},{"code":"4304","name":"衡阳市","children":[{"code":"430405","name":"珠晖区"},{"code":"430406","name":"雁峰区"},{"code":"430407","name":"石鼓区"},{"code":"430408","name":"蒸湘区"},{"code":"430412","name":"南岳区"},{"code":"430421","name":"衡阳县"},{"code":"430422","name":"衡南县"},{"code":"430423","name":"衡山县"},{"code":"430424","name":"衡东县"},{"code":"430426","name":"祁东县"},{"code":"430471","name":"衡阳综合保税区"},{"code":"430472","name":"湖南衡阳高新技术产业园区"},{"code":"430473","name":"湖南衡阳松木经济开发区"},{"code":"430481","name":"耒阳市"},{"code":"430482","name":"常宁市"}]},{"code":"4305","name":"邵阳市","children":[{"code":"430502","name":"双清区"},{"code":"430503","name":"大祥区"},{"code":"430511","name":"北塔区"},{"code":"430522","name":"新邵县"},{"code":"430523","name":"邵阳县"},{"code":"430524","name":"隆回县"},{"code":"430525","name":"洞口县"},{"code":"430527","name":"绥宁县"},{"code":"430528","name":"新宁县"},{"code":"430529","name":"城步苗族自治县"},{"code":"430581","name":"武冈市"},{"code":"430582","name":"邵东市"}]},{"code":"4306","name":"岳阳市","children":[{"code":"430602","name":"岳阳楼区"},{"code":"430603","name":"云溪区"},{"code":"430611","name":"君山区"},{"code":"430621","name":"岳阳县"},{"code":"430623","name":"华容县"},{"code":"430624","name":"湘阴县"},{"code":"430626","name":"平江县"},{"code":"430671","name":"岳阳市屈原管理区"},{"code":"430681","name":"汨罗市"},{"code":"430682","name":"临湘市"}]},{"code":"4307","name":"常德市","children":[{"code":"430702","name":"武陵区"},{"code":"430703","name":"鼎城区"},{"code":"430721","name":"安乡县"},{"code":"430722","name":"汉寿县"},{"code":"430723","name":"澧县"},{"code":"430724","name":"临澧县"},{"code":"430725","name":"桃源县"},{"code":"430726","name":"石门县"},{"code":"430771","name":"常德市西洞庭管理区"},{"code":"430781","name":"津市市"}]},{"code":"4308","name":"张家界市","children":[{"code":"430802","name":"永定区"},{"code":"430811","name":"武陵源区"},{"code":"430821","name":"慈利县"},{"code":"430822","name":"桑植县"}]},{"code":"4309","name":"益阳市","children":[{"code":"430902","name":"资阳区"},{"code":"430903","name":"赫山区"},{"code":"430921","name":"南县"},{"code":"430922","name":"桃江县"},{"code":"430923","name":"安化县"},{"code":"430971","name":"益阳市大通湖管理区"},{"code":"430972","name":"湖南益阳高新技术产业园区"},{"code":"430981","name":"沅江市"}]},{"code":"4310","name":"郴州市","children":[{"code":"431002","name":"北湖区"},{"code":"431003","name":"苏仙区"},{"code":"431021","name":"桂阳县"},{"code":"431022","name":"宜章县"},{"code":"431023","name":"永兴县"},{"code":"431024","name":"嘉禾县"},{"code":"431025","name":"临武县"},{"code":"431026","name":"汝城县"},{"code":"431027","name":"桂东县"},{"code":"431028","name":"安仁县"},{"code":"431081","name":"资兴市"}]},{"code":"4311","name":"永州市","children":[{"code":"431102","name":"零陵区"},{"code":"431103","name":"冷水滩区"},{"code":"431121","name":"祁阳县"},{"code":"431122","name":"东安县"},{"code":"431123","name":"双牌县"},{"code":"431124","name":"道县"},{"code":"431125","name":"江永县"},{"code":"431126","name":"宁远县"},{"code":"431127","name":"蓝山县"},{"code":"431128","name":"新田县"},{"code":"431129","name":"江华瑶族自治县"},{"code":"431171","name":"永州经济技术开发区"},{"code":"431172","name":"永州市金洞管理区"},{"code":"431173","name":"永州市回龙圩管理区"}]},{"code":"4312","name":"怀化市","children":[{"code":"431202","name":"鹤城区"},{"code":"431221","name":"中方县"},{"code":"431222","name":"沅陵县"},{"code":"431223","name":"辰溪县"},{"code":"431224","name":"溆浦县"},{"code":"431225","name":"会同县"},{"code":"431226","name":"麻阳苗族自治县"},{"code":"431227","name":"新晃侗族自治县"},{"code":"431228","name":"芷江侗族自治县"},{"code":"431229","name":"靖州苗族侗族自治县"},{"code":"431230","name":"通道侗族自治县"},{"code":"431271","name":"怀化市洪江管理区"},{"code":"431281","name":"洪江市"}]},{"code":"4313","name":"娄底市","children":[{"code":"431302","name":"娄星区"},{"code":"431321","name":"双峰县"},{"code":"431322","name":"新化县"},{"code":"431381","name":"冷水江市"},{"code":"431382","name":"涟源市"}]},{"code":"4331","name":"湘西土家族苗族自治州","children":[{"code":"433101","name":"吉首市"},{"code":"433122","name":"泸溪县"},{"code":"433123","name":"凤凰县"},{"code":"433124","name":"花垣县"},{"code":"433125","name":"保靖县"},{"code":"433126","name":"古丈县"},{"code":"433127","name":"永顺县"},{"code":"433130","name":"龙山县"}]}]},{"code":"44","name":"广东省","children":[{"code":"4401","name":"广州市","children":[{"code":"440103","name":"荔湾区"},{"code":"440104","name":"越秀区"},{"code":"440105","name":"海珠区"},{"code":"440106","name":"天河区"},{"code":"440111","name":"白云区"},{"code":"440112","name":"黄埔区"},{"code":"440113","name":"番禺区"},{"code":"440114","name":"花都区"},{"code":"440115","name":"南沙区"},{"code":"440117","name":"从化区"},{"code":"440118","name":"增城区"}]},{"code":"4402","name":"韶关市","children":[{"code":"440203","name":"武江区"},{"code":"440204","name":"浈江区"},{"code":"440205","name":"曲江区"},{"code":"440222","name":"始兴县"},{"code":"440224","name":"仁化县"},{"code":"440229","name":"翁源县"},{"code":"440232","name":"乳源瑶族自治县"},{"code":"440233","name":"新丰县"},{"code":"440281","name":"乐昌市"},{"code":"440282","name":"南雄市"}]},{"code":"4403","name":"深圳市","children":[{"code":"440303","name":"罗湖区"},{"code":"440304","name":"福田区"},{"code":"440305","name":"南山区"},{"code":"440306","name":"宝安区"},{"code":"440307","name":"龙岗区"},{"code":"440308","name":"盐田区"},{"code":"440309","name":"龙华区"},{"code":"440310","name":"坪山区"},{"code":"440311","name":"光明区"}]},{"code":"4404","name":"珠海市","children":[{"code":"440402","name":"香洲区"},{"code":"440403","name":"斗门区"},{"code":"440404","name":"金湾区"}]},{"code":"4405","name":"汕头市","children":[{"code":"440507","name":"龙湖区"},{"code":"440511","name":"金平区"},{"code":"440512","name":"濠江区"},{"code":"440513","name":"潮阳区"},{"code":"440514","name":"潮南区"},{"code":"440515","name":"澄海区"},{"code":"440523","name":"南澳县"}]},{"code":"4406","name":"佛山市","children":[{"code":"440604","name":"禅城区"},{"code":"440605","name":"南海区"},{"code":"440606","name":"顺德区"},{"code":"440607","name":"三水区"},{"code":"440608","name":"高明区"}]},{"code":"4407","name":"江门市","children":[{"code":"440703","name":"蓬江区"},{"code":"440704","name":"江海区"},{"code":"440705","name":"新会区"},{"code":"440781","name":"台山市"},{"code":"440783","name":"开平市"},{"code":"440784","name":"鹤山市"},{"code":"440785","name":"恩平市"}]},{"code":"4408","name":"湛江市","children":[{"code":"440802","name":"赤坎区"},{"code":"440803","name":"霞山区"},{"code":"440804","name":"坡头区"},{"code":"440811","name":"麻章区"},{"code":"440823","name":"遂溪县"},{"code":"440825","name":"徐闻县"},{"code":"440881","name":"廉江市"},{"code":"440882","name":"雷州市"},{"code":"440883","name":"吴川市"}]},{"code":"4409","name":"茂名市","children":[{"code":"440902","name":"茂南区"},{"code":"440904","name":"电白区"},{"code":"440981","name":"高州市"},{"code":"440982","name":"化州市"},{"code":"440983","name":"信宜市"}]},{"code":"4412","name":"肇庆市","children":[{"code":"441202","name":"端州区"},{"code":"441203","name":"鼎湖区"},{"code":"441204","name":"高要区"},{"code":"441223","name":"广宁县"},{"code":"441224","name":"怀集县"},{"code":"441225","name":"封开县"},{"code":"441226","name":"德庆县"},{"code":"441284","name":"四会市"}]},{"code":"4413","name":"惠州市","children":[{"code":"441302","name":"惠城区"},{"code":"441303","name":"惠阳区"},{"code":"441322","name":"博罗县"},{"code":"441323","name":"惠东县"},{"code":"441324","name":"龙门县"}]},{"code":"4414","name":"梅州市","children":[{"code":"441402","name":"梅江区"},{"code":"441403","name":"梅县区"},{"code":"441422","name":"大埔县"},{"code":"441423","name":"丰顺县"},{"code":"441424","name":"五华县"},{"code":"441426","name":"平远县"},{"code":"441427","name":"蕉岭县"},{"code":"441481","name":"兴宁市"}]},{"code":"4415","name":"汕尾市","children":[{"code":"441502","name":"城区"},{"code":"441521","name":"海丰县"},{"code":"441523","name":"陆河县"},{"code":"441581","name":"陆丰市"}]},{"code":"4416","name":"河源市","children":[{"code":"441602","name":"源城区"},{"code":"441621","name":"紫金县"},{"code":"441622","name":"龙川县"},{"code":"441623","name":"连平县"},{"code":"441624","name":"和平县"},{"code":"441625","name":"东源县"}]},{"code":"4417","name":"阳江市","children":[{"code":"441702","name":"江城区"},{"code":"441704","name":"阳东区"},{"code":"441721","name":"阳西县"},{"code":"441781","name":"阳春市"}]},{"code":"4418","name":"清远市","children":[{"code":"441802","name":"清城区"},{"code":"441803","name":"清新区"},{"code":"441821","name":"佛冈县"},{"code":"441823","name":"阳山县"},{"code":"441825","name":"连山壮族瑶族自治县"},{"code":"441826","name":"连南瑶族自治县"},{"code":"441881","name":"英德市"},{"code":"441882","name":"连州市"}]},{"code":"4419","name":"东莞市","children":[{"code":"441900003","name":"东城街道"},{"code":"441900004","name":"南城街道"},{"code":"441900005","name":"万江街道"},{"code":"441900006","name":"莞城街道"},{"code":"441900101","name":"石碣镇"},{"code":"441900102","name":"石龙镇"},{"code":"441900103","name":"茶山镇"},{"code":"441900104","name":"石排镇"},{"code":"441900105","name":"企石镇"},{"code":"441900106","name":"横沥镇"},{"code":"441900107","name":"桥头镇"},{"code":"441900108","name":"谢岗镇"},{"code":"441900109","name":"东坑镇"},{"code":"441900110","name":"常平镇"},{"code":"441900111","name":"寮步镇"},{"code":"441900112","name":"樟木头镇"},{"code":"441900113","name":"大朗镇"},{"code":"441900114","name":"黄江镇"},{"code":"441900115","name":"清溪镇"},{"code":"441900116","name":"塘厦镇"},{"code":"441900117","name":"凤岗镇"},{"code":"441900118","name":"大岭山镇"},{"code":"441900119","name":"长安镇"},{"code":"441900121","name":"虎门镇"},{"code":"441900122","name":"厚街镇"},{"code":"441900123","name":"沙田镇"},{"code":"441900124","name":"道滘镇"},{"code":"441900125","name":"洪梅镇"},{"code":"441900126","name":"麻涌镇"},{"code":"441900127","name":"望牛墩镇"},{"code":"441900128","name":"中堂镇"},{"code":"441900129","name":"高埗镇"},{"code":"441900401","name":"松山湖"},{"code":"441900402","name":"东莞港"},{"code":"441900403","name":"东莞生态园"}]},{"code":"4420","name":"中山市","children":[{"code":"442000001","name":"石岐街道"},{"code":"442000002","name":"东区街道"},{"code":"442000003","name":"中山港街道"},{"code":"442000004","name":"西区街道"},{"code":"442000005","name":"南区街道"},{"code":"442000006","name":"五桂山街道"},{"code":"442000100","name":"小榄镇"},{"code":"442000101","name":"黄圃镇"},{"code":"442000102","name":"民众镇"},{"code":"442000103","name":"东凤镇"},{"code":"442000104","name":"东升镇"},{"code":"442000105","name":"古镇镇"},{"code":"442000106","name":"沙溪镇"},{"code":"442000107","name":"坦洲镇"},{"code":"442000108","name":"港口镇"},{"code":"442000109","name":"三角镇"},{"code":"442000110","name":"横栏镇"},{"code":"442000111","name":"南头镇"},{"code":"442000112","name":"阜沙镇"},{"code":"442000113","name":"南朗镇"},{"code":"442000114","name":"三乡镇"},{"code":"442000115","name":"板芙镇"},{"code":"442000116","name":"大涌镇"},{"code":"442000117","name":"神湾镇"}]},{"code":"4451","name":"潮州市","children":[{"code":"445102","name":"湘桥区"},{"code":"445103","name":"潮安区"},{"code":"445122","name":"饶平县"}]},{"code":"4452","name":"揭阳市","children":[{"code":"445202","name":"榕城区"},{"code":"445203","name":"揭东区"},{"code":"445222","name":"揭西县"},{"code":"445224","name":"惠来县"},{"code":"445281","name":"普宁市"}]},{"code":"4453","name":"云浮市","children":[{"code":"445302","name":"云城区"},{"code":"445303","name":"云安区"},{"code":"445321","name":"新兴县"},{"code":"445322","name":"郁南县"},{"code":"445381","name":"罗定市"}]}]},{"code":"45","name":"广西壮族自治区","children":[{"code":"4501","name":"南宁市","children":[{"code":"450102","name":"兴宁区"},{"code":"450103","name":"青秀区"},{"code":"450105","name":"江南区"},{"code":"450107","name":"西乡塘区"},{"code":"450108","name":"良庆区"},{"code":"450109","name":"邕宁区"},{"code":"450110","name":"武鸣区"},{"code":"450123","name":"隆安县"},{"code":"450124","name":"马山县"},{"code":"450125","name":"上林县"},{"code":"450126","name":"宾阳县"},{"code":"450127","name":"横县"}]},{"code":"4502","name":"柳州市","children":[{"code":"450202","name":"城中区"},{"code":"450203","name":"鱼峰区"},{"code":"450204","name":"柳南区"},{"code":"450205","name":"柳北区"},{"code":"450206","name":"柳江区"},{"code":"450222","name":"柳城县"},{"code":"450223","name":"鹿寨县"},{"code":"450224","name":"融安县"},{"code":"450225","name":"融水苗族自治县"},{"code":"450226","name":"三江侗族自治县"}]},{"code":"4503","name":"桂林市","children":[{"code":"450302","name":"秀峰区"},{"code":"450303","name":"叠彩区"},{"code":"450304","name":"象山区"},{"code":"450305","name":"七星区"},{"code":"450311","name":"雁山区"},{"code":"450312","name":"临桂区"},{"code":"450321","name":"阳朔县"},{"code":"450323","name":"灵川县"},{"code":"450324","name":"全州县"},{"code":"450325","name":"兴安县"},{"code":"450326","name":"永福县"},{"code":"450327","name":"灌阳县"},{"code":"450328","name":"龙胜各族自治县"},{"code":"450329","name":"资源县"},{"code":"450330","name":"平乐县"},{"code":"450332","name":"恭城瑶族自治县"},{"code":"450381","name":"荔浦市"}]},{"code":"4504","name":"梧州市","children":[{"code":"450403","name":"万秀区"},{"code":"450405","name":"长洲区"},{"code":"450406","name":"龙圩区"},{"code":"450421","name":"苍梧县"},{"code":"450422","name":"藤县"},{"code":"450423","name":"蒙山县"},{"code":"450481","name":"岑溪市"}]},{"code":"4505","name":"北海市","children":[{"code":"450502","name":"海城区"},{"code":"450503","name":"银海区"},{"code":"450512","name":"铁山港区"},{"code":"450521","name":"合浦县"}]},{"code":"4506","name":"防城港市","children":[{"code":"450602","name":"港口区"},{"code":"450603","name":"防城区"},{"code":"450621","name":"上思县"},{"code":"450681","name":"东兴市"}]},{"code":"4507","name":"钦州市","children":[{"code":"450702","name":"钦南区"},{"code":"450703","name":"钦北区"},{"code":"450721","name":"灵山县"},{"code":"450722","name":"浦北县"}]},{"code":"4508","name":"贵港市","children":[{"code":"450802","name":"港北区"},{"code":"450803","name":"港南区"},{"code":"450804","name":"覃塘区"},{"code":"450821","name":"平南县"},{"code":"450881","name":"桂平市"}]},{"code":"4509","name":"玉林市","children":[{"code":"450902","name":"玉州区"},{"code":"450903","name":"福绵区"},{"code":"450921","name":"容县"},{"code":"450922","name":"陆川县"},{"code":"450923","name":"博白县"},{"code":"450924","name":"兴业县"},{"code":"450981","name":"北流市"}]},{"code":"4510","name":"百色市","children":[{"code":"451002","name":"右江区"},{"code":"451003","name":"田阳区"},{"code":"451022","name":"田东县"},{"code":"451024","name":"德保县"},{"code":"451026","name":"那坡县"},{"code":"451027","name":"凌云县"},{"code":"451028","name":"乐业县"},{"code":"451029","name":"田林县"},{"code":"451030","name":"西林县"},{"code":"451031","name":"隆林各族自治县"},{"code":"451081","name":"靖西市"},{"code":"451082","name":"平果市"}]},{"code":"4511","name":"贺州市","children":[{"code":"451102","name":"八步区"},{"code":"451103","name":"平桂区"},{"code":"451121","name":"昭平县"},{"code":"451122","name":"钟山县"},{"code":"451123","name":"富川瑶族自治县"}]},{"code":"4512","name":"河池市","children":[{"code":"451202","name":"金城江区"},{"code":"451203","name":"宜州区"},{"code":"451221","name":"南丹县"},{"code":"451222","name":"天峨县"},{"code":"451223","name":"凤山县"},{"code":"451224","name":"东兰县"},{"code":"451225","name":"罗城仫佬族自治县"},{"code":"451226","name":"环江毛南族自治县"},{"code":"451227","name":"巴马瑶族自治县"},{"code":"451228","name":"都安瑶族自治县"},{"code":"451229","name":"大化瑶族自治县"}]},{"code":"4513","name":"来宾市","children":[{"code":"451302","name":"兴宾区"},{"code":"451321","name":"忻城县"},{"code":"451322","name":"象州县"},{"code":"451323","name":"武宣县"},{"code":"451324","name":"金秀瑶族自治县"},{"code":"451381","name":"合山市"}]},{"code":"4514","name":"崇左市","children":[{"code":"451402","name":"江州区"},{"code":"451421","name":"扶绥县"},{"code":"451422","name":"宁明县"},{"code":"451423","name":"龙州县"},{"code":"451424","name":"大新县"},{"code":"451425","name":"天等县"},{"code":"451481","name":"凭祥市"}]}]},{"code":"46","name":"海南省","children":[{"code":"4601","name":"海口市","children":[{"code":"460105","name":"秀英区"},{"code":"460106","name":"龙华区"},{"code":"460107","name":"琼山区"},{"code":"460108","name":"美兰区"}]},{"code":"4602","name":"三亚市","children":[{"code":"460202","name":"海棠区"},{"code":"460203","name":"吉阳区"},{"code":"460204","name":"天涯区"},{"code":"460205","name":"崖州区"}]},{"code":"4603","name":"三沙市","children":[{"code":"460321","name":"西沙群岛"},{"code":"460322","name":"南沙群岛"},{"code":"460323","name":"中沙群岛的岛礁及其海域"}]},{"code":"4604","name":"儋州市","children":[{"code":"460400100","name":"那大镇"},{"code":"460400101","name":"和庆镇"},{"code":"460400102","name":"南丰镇"},{"code":"460400103","name":"大成镇"},{"code":"460400104","name":"雅星镇"},{"code":"460400105","name":"兰洋镇"},{"code":"460400106","name":"光村镇"},{"code":"460400107","name":"木棠镇"},{"code":"460400108","name":"海头镇"},{"code":"460400109","name":"峨蔓镇"},{"code":"460400111","name":"王五镇"},{"code":"460400112","name":"白马井镇"},{"code":"460400113","name":"中和镇"},{"code":"460400114","name":"排浦镇"},{"code":"460400115","name":"东成镇"},{"code":"460400116","name":"新州镇"},{"code":"460400499","name":"洋浦经济开发区"},{"code":"460400500","name":"华南热作学院"}]},{"code":"4690","name":"省直辖县级行政区划","children":[{"code":"469001","name":"五指山市"},{"code":"469002","name":"琼海市"},{"code":"469005","name":"文昌市"},{"code":"469006","name":"万宁市"},{"code":"469007","name":"东方市"},{"code":"469021","name":"定安县"},{"code":"469022","name":"屯昌县"},{"code":"469023","name":"澄迈县"},{"code":"469024","name":"临高县"},{"code":"469025","name":"白沙黎族自治县"},{"code":"469026","name":"昌江黎族自治县"},{"code":"469027","name":"乐东黎族自治县"},{"code":"469028","name":"陵水黎族自治县"},{"code":"469029","name":"保亭黎族苗族自治县"},{"code":"469030","name":"琼中黎族苗族自治县"}]}]},{"code":"50","name":"重庆市","children":[{"code":"5001","name":"市辖区","children":[{"code":"500101","name":"万州区"},{"code":"500102","name":"涪陵区"},{"code":"500103","name":"渝中区"},{"code":"500104","name":"大渡口区"},{"code":"500105","name":"江北区"},{"code":"500106","name":"沙坪坝区"},{"code":"500107","name":"九龙坡区"},{"code":"500108","name":"南岸区"},{"code":"500109","name":"北碚区"},{"code":"500110","name":"綦江区"},{"code":"500111","name":"大足区"},{"code":"500112","name":"渝北区"},{"code":"500113","name":"巴南区"},{"code":"500114","name":"黔江区"},{"code":"500115","name":"长寿区"},{"code":"500116","name":"江津区"},{"code":"500117","name":"合川区"},{"code":"500118","name":"永川区"},{"code":"500119","name":"南川区"},{"code":"500120","name":"璧山区"},{"code":"500151","name":"铜梁区"},{"code":"500152","name":"潼南区"},{"code":"500153","name":"荣昌区"},{"code":"500154","name":"开州区"},{"code":"500155","name":"梁平区"},{"code":"500156","name":"武隆区"}]},{"code":"5002","name":"县","children":[{"code":"500229","name":"城口县"},{"code":"500230","name":"丰都县"},{"code":"500231","name":"垫江县"},{"code":"500233","name":"忠县"},{"code":"500235","name":"云阳县"},{"code":"500236","name":"奉节县"},{"code":"500237","name":"巫山县"},{"code":"500238","name":"巫溪县"},{"code":"500240","name":"石柱土家族自治县"},{"code":"500241","name":"秀山土家族苗族自治县"},{"code":"500242","name":"酉阳土家族苗族自治县"},{"code":"500243","name":"彭水苗族土家族自治县"}]}]},{"code":"51","name":"四川省","children":[{"code":"5101","name":"成都市","children":[{"code":"510104","name":"锦江区"},{"code":"510105","name":"青羊区"},{"code":"510106","name":"金牛区"},{"code":"510107","name":"武侯区"},{"code":"510108","name":"成华区"},{"code":"510112","name":"龙泉驿区"},{"code":"510113","name":"青白江区"},{"code":"510114","name":"新都区"},{"code":"510115","name":"温江区"},{"code":"510116","name":"双流区"},{"code":"510117","name":"郫都区"},{"code":"510118","name":"新津区"},{"code":"510121","name":"金堂县"},{"code":"510129","name":"大邑县"},{"code":"510131","name":"蒲江县"},{"code":"510181","name":"都江堰市"},{"code":"510182","name":"彭州市"},{"code":"510183","name":"邛崃市"},{"code":"510184","name":"崇州市"},{"code":"510185","name":"简阳市"}]},{"code":"5103","name":"自贡市","children":[{"code":"510302","name":"自流井区"},{"code":"510303","name":"贡井区"},{"code":"510304","name":"大安区"},{"code":"510311","name":"沿滩区"},{"code":"510321","name":"荣县"},{"code":"510322","name":"富顺县"}]},{"code":"5104","name":"攀枝花市","children":[{"code":"510402","name":"东区"},{"code":"510403","name":"西区"},{"code":"510411","name":"仁和区"},{"code":"510421","name":"米易县"},{"code":"510422","name":"盐边县"}]},{"code":"5105","name":"泸州市","children":[{"code":"510502","name":"江阳区"},{"code":"510503","name":"纳溪区"},{"code":"510504","name":"龙马潭区"},{"code":"510521","name":"泸县"},{"code":"510522","name":"合江县"},{"code":"510524","name":"叙永县"},{"code":"510525","name":"古蔺县"}]},{"code":"5106","name":"德阳市","children":[{"code":"510603","name":"旌阳区"},{"code":"510604","name":"罗江区"},{"code":"510623","name":"中江县"},{"code":"510681","name":"广汉市"},{"code":"510682","name":"什邡市"},{"code":"510683","name":"绵竹市"}]},{"code":"5107","name":"绵阳市","children":[{"code":"510703","name":"涪城区"},{"code":"510704","name":"游仙区"},{"code":"510705","name":"安州区"},{"code":"510722","name":"三台县"},{"code":"510723","name":"盐亭县"},{"code":"510725","name":"梓潼县"},{"code":"510726","name":"北川羌族自治县"},{"code":"510727","name":"平武县"},{"code":"510781","name":"江油市"}]},{"code":"5108","name":"广元市","children":[{"code":"510802","name":"利州区"},{"code":"510811","name":"昭化区"},{"code":"510812","name":"朝天区"},{"code":"510821","name":"旺苍县"},{"code":"510822","name":"青川县"},{"code":"510823","name":"剑阁县"},{"code":"510824","name":"苍溪县"}]},{"code":"5109","name":"遂宁市","children":[{"code":"510903","name":"船山区"},{"code":"510904","name":"安居区"},{"code":"510921","name":"蓬溪县"},{"code":"510923","name":"大英县"},{"code":"510981","name":"射洪市"}]},{"code":"5110","name":"内江市","children":[{"code":"511002","name":"市中区"},{"code":"511011","name":"东兴区"},{"code":"511024","name":"威远县"},{"code":"511025","name":"资中县"},{"code":"511071","name":"内江经济开发区"},{"code":"511083","name":"隆昌市"}]},{"code":"5111","name":"乐山市","children":[{"code":"511102","name":"市中区"},{"code":"511111","name":"沙湾区"},{"code":"511112","name":"五通桥区"},{"code":"511113","name":"金口河区"},{"code":"511123","name":"犍为县"},{"code":"511124","name":"井研县"},{"code":"511126","name":"夹江县"},{"code":"511129","name":"沐川县"},{"code":"511132","name":"峨边彝族自治县"},{"code":"511133","name":"马边彝族自治县"},{"code":"511181","name":"峨眉山市"}]},{"code":"5113","name":"南充市","children":[{"code":"511302","name":"顺庆区"},{"code":"511303","name":"高坪区"},{"code":"511304","name":"嘉陵区"},{"code":"511321","name":"南部县"},{"code":"511322","name":"营山县"},{"code":"511323","name":"蓬安县"},{"code":"511324","name":"仪陇县"},{"code":"511325","name":"西充县"},{"code":"511381","name":"阆中市"}]},{"code":"5114","name":"眉山市","children":[{"code":"511402","name":"东坡区"},{"code":"511403","name":"彭山区"},{"code":"511421","name":"仁寿县"},{"code":"511423","name":"洪雅县"},{"code":"511424","name":"丹棱县"},{"code":"511425","name":"青神县"}]},{"code":"5115","name":"宜宾市","children":[{"code":"511502","name":"翠屏区"},{"code":"511503","name":"南溪区"},{"code":"511504","name":"叙州区"},{"code":"511523","name":"江安县"},{"code":"511524","name":"长宁县"},{"code":"511525","name":"高县"},{"code":"511526","name":"珙县"},{"code":"511527","name":"筠连县"},{"code":"511528","name":"兴文县"},{"code":"511529","name":"屏山县"}]},{"code":"5116","name":"广安市","children":[{"code":"511602","name":"广安区"},{"code":"511603","name":"前锋区"},{"code":"511621","name":"岳池县"},{"code":"511622","name":"武胜县"},{"code":"511623","name":"邻水县"},{"code":"511681","name":"华蓥市"}]},{"code":"5117","name":"达州市","children":[{"code":"511702","name":"通川区"},{"code":"511703","name":"达川区"},{"code":"511722","name":"宣汉县"},{"code":"511723","name":"开江县"},{"code":"511724","name":"大竹县"},{"code":"511725","name":"渠县"},{"code":"511771","name":"达州经济开发区"},{"code":"511781","name":"万源市"}]},{"code":"5118","name":"雅安市","children":[{"code":"511802","name":"雨城区"},{"code":"511803","name":"名山区"},{"code":"511822","name":"荥经县"},{"code":"511823","name":"汉源县"},{"code":"511824","name":"石棉县"},{"code":"511825","name":"天全县"},{"code":"511826","name":"芦山县"},{"code":"511827","name":"宝兴县"}]},{"code":"5119","name":"巴中市","children":[{"code":"511902","name":"巴州区"},{"code":"511903","name":"恩阳区"},{"code":"511921","name":"通江县"},{"code":"511922","name":"南江县"},{"code":"511923","name":"平昌县"},{"code":"511971","name":"巴中经济开发区"}]},{"code":"5120","name":"资阳市","children":[{"code":"512002","name":"雁江区"},{"code":"512021","name":"安岳县"},{"code":"512022","name":"乐至县"}]},{"code":"5132","name":"阿坝藏族羌族自治州","children":[{"code":"513201","name":"马尔康市"},{"code":"513221","name":"汶川县"},{"code":"513222","name":"理县"},{"code":"513223","name":"茂县"},{"code":"513224","name":"松潘县"},{"code":"513225","name":"九寨沟县"},{"code":"513226","name":"金川县"},{"code":"513227","name":"小金县"},{"code":"513228","name":"黑水县"},{"code":"513230","name":"壤塘县"},{"code":"513231","name":"阿坝县"},{"code":"513232","name":"若尔盖县"},{"code":"513233","name":"红原县"}]},{"code":"5133","name":"甘孜藏族自治州","children":[{"code":"513301","name":"康定市"},{"code":"513322","name":"泸定县"},{"code":"513323","name":"丹巴县"},{"code":"513324","name":"九龙县"},{"code":"513325","name":"雅江县"},{"code":"513326","name":"道孚县"},{"code":"513327","name":"炉霍县"},{"code":"513328","name":"甘孜县"},{"code":"513329","name":"新龙县"},{"code":"513330","name":"德格县"},{"code":"513331","name":"白玉县"},{"code":"513332","name":"石渠县"},{"code":"513333","name":"色达县"},{"code":"513334","name":"理塘县"},{"code":"513335","name":"巴塘县"},{"code":"513336","name":"乡城县"},{"code":"513337","name":"稻城县"},{"code":"513338","name":"得荣县"}]},{"code":"5134","name":"凉山彝族自治州","children":[{"code":"513401","name":"西昌市"},{"code":"513422","name":"木里藏族自治县"},{"code":"513423","name":"盐源县"},{"code":"513424","name":"德昌县"},{"code":"513425","name":"会理县"},{"code":"513426","name":"会东县"},{"code":"513427","name":"宁南县"},{"code":"513428","name":"普格县"},{"code":"513429","name":"布拖县"},{"code":"513430","name":"金阳县"},{"code":"513431","name":"昭觉县"},{"code":"513432","name":"喜德县"},{"code":"513433","name":"冕宁县"},{"code":"513434","name":"越西县"},{"code":"513435","name":"甘洛县"},{"code":"513436","name":"美姑县"},{"code":"513437","name":"雷波县"}]}]},{"code":"52","name":"贵州省","children":[{"code":"5201","name":"贵阳市","children":[{"code":"520102","name":"南明区"},{"code":"520103","name":"云岩区"},{"code":"520111","name":"花溪区"},{"code":"520112","name":"乌当区"},{"code":"520113","name":"白云区"},{"code":"520115","name":"观山湖区"},{"code":"520121","name":"开阳县"},{"code":"520122","name":"息烽县"},{"code":"520123","name":"修文县"},{"code":"520181","name":"清镇市"}]},{"code":"5202","name":"六盘水市","children":[{"code":"520201","name":"钟山区"},{"code":"520203","name":"六枝特区"},{"code":"520221","name":"水城县"},{"code":"520281","name":"盘州市"}]},{"code":"5203","name":"遵义市","children":[{"code":"520302","name":"红花岗区"},{"code":"520303","name":"汇川区"},{"code":"520304","name":"播州区"},{"code":"520322","name":"桐梓县"},{"code":"520323","name":"绥阳县"},{"code":"520324","name":"正安县"},{"code":"520325","name":"道真仡佬族苗族自治县"},{"code":"520326","name":"务川仡佬族苗族自治县"},{"code":"520327","name":"凤冈县"},{"code":"520328","name":"湄潭县"},{"code":"520329","name":"余庆县"},{"code":"520330","name":"习水县"},{"code":"520381","name":"赤水市"},{"code":"520382","name":"仁怀市"}]},{"code":"5204","name":"安顺市","children":[{"code":"520402","name":"西秀区"},{"code":"520403","name":"平坝区"},{"code":"520422","name":"普定县"},{"code":"520423","name":"镇宁布依族苗族自治县"},{"code":"520424","name":"关岭布依族苗族自治县"},{"code":"520425","name":"紫云苗族布依族自治县"}]},{"code":"5205","name":"毕节市","children":[{"code":"520502","name":"七星关区"},{"code":"520521","name":"大方县"},{"code":"520522","name":"黔西县"},{"code":"520523","name":"金沙县"},{"code":"520524","name":"织金县"},{"code":"520525","name":"纳雍县"},{"code":"520526","name":"威宁彝族回族苗族自治县"},{"code":"520527","name":"赫章县"}]},{"code":"5206","name":"铜仁市","children":[{"code":"520602","name":"碧江区"},{"code":"520603","name":"万山区"},{"code":"520621","name":"江口县"},{"code":"520622","name":"玉屏侗族自治县"},{"code":"520623","name":"石阡县"},{"code":"520624","name":"思南县"},{"code":"520625","name":"印江土家族苗族自治县"},{"code":"520626","name":"德江县"},{"code":"520627","name":"沿河土家族自治县"},{"code":"520628","name":"松桃苗族自治县"}]},{"code":"5223","name":"黔西南布依族苗族自治州","children":[{"code":"522301","name":"兴义市"},{"code":"522302","name":"兴仁市"},{"code":"522323","name":"普安县"},{"code":"522324","name":"晴隆县"},{"code":"522325","name":"贞丰县"},{"code":"522326","name":"望谟县"},{"code":"522327","name":"册亨县"},{"code":"522328","name":"安龙县"}]},{"code":"5226","name":"黔东南苗族侗族自治州","children":[{"code":"522601","name":"凯里市"},{"code":"522622","name":"黄平县"},{"code":"522623","name":"施秉县"},{"code":"522624","name":"三穗县"},{"code":"522625","name":"镇远县"},{"code":"522626","name":"岑巩县"},{"code":"522627","name":"天柱县"},{"code":"522628","name":"锦屏县"},{"code":"522629","name":"剑河县"},{"code":"522630","name":"台江县"},{"code":"522631","name":"黎平县"},{"code":"522632","name":"榕江县"},{"code":"522633","name":"从江县"},{"code":"522634","name":"雷山县"},{"code":"522635","name":"麻江县"},{"code":"522636","name":"丹寨县"}]},{"code":"5227","name":"黔南布依族苗族自治州","children":[{"code":"522701","name":"都匀市"},{"code":"522702","name":"福泉市"},{"code":"522722","name":"荔波县"},{"code":"522723","name":"贵定县"},{"code":"522725","name":"瓮安县"},{"code":"522726","name":"独山县"},{"code":"522727","name":"平塘县"},{"code":"522728","name":"罗甸县"},{"code":"522729","name":"长顺县"},{"code":"522730","name":"龙里县"},{"code":"522731","name":"惠水县"},{"code":"522732","name":"三都水族自治县"}]}]},{"code":"53","name":"云南省","children":[{"code":"5301","name":"昆明市","children":[{"code":"530102","name":"五华区"},{"code":"530103","name":"盘龙区"},{"code":"530111","name":"官渡区"},{"code":"530112","name":"西山区"},{"code":"530113","name":"东川区"},{"code":"530114","name":"呈贡区"},{"code":"530115","name":"晋宁区"},{"code":"530124","name":"富民县"},{"code":"530125","name":"宜良县"},{"code":"530126","name":"石林彝族自治县"},{"code":"530127","name":"嵩明县"},{"code":"530128","name":"禄劝彝族苗族自治县"},{"code":"530129","name":"寻甸回族彝族自治县"},{"code":"530181","name":"安宁市"}]},{"code":"5303","name":"曲靖市","children":[{"code":"530302","name":"麒麟区"},{"code":"530303","name":"沾益区"},{"code":"530304","name":"马龙区"},{"code":"530322","name":"陆良县"},{"code":"530323","name":"师宗县"},{"code":"530324","name":"罗平县"},{"code":"530325","name":"富源县"},{"code":"530326","name":"会泽县"},{"code":"530381","name":"宣威市"}]},{"code":"5304","name":"玉溪市","children":[{"code":"530402","name":"红塔区"},{"code":"530403","name":"江川区"},{"code":"530423","name":"通海县"},{"code":"530424","name":"华宁县"},{"code":"530425","name":"易门县"},{"code":"530426","name":"峨山彝族自治县"},{"code":"530427","name":"新平彝族傣族自治县"},{"code":"530428","name":"元江哈尼族彝族傣族自治县"},{"code":"530481","name":"澄江市"}]},{"code":"5305","name":"保山市","children":[{"code":"530502","name":"隆阳区"},{"code":"530521","name":"施甸县"},{"code":"530523","name":"龙陵县"},{"code":"530524","name":"昌宁县"},{"code":"530581","name":"腾冲市"}]},{"code":"5306","name":"昭通市","children":[{"code":"530602","name":"昭阳区"},{"code":"530621","name":"鲁甸县"},{"code":"530622","name":"巧家县"},{"code":"530623","name":"盐津县"},{"code":"530624","name":"大关县"},{"code":"530625","name":"永善县"},{"code":"530626","name":"绥江县"},{"code":"530627","name":"镇雄县"},{"code":"530628","name":"彝良县"},{"code":"530629","name":"威信县"},{"code":"530681","name":"水富市"}]},{"code":"5307","name":"丽江市","children":[{"code":"530702","name":"古城区"},{"code":"530721","name":"玉龙纳西族自治县"},{"code":"530722","name":"永胜县"},{"code":"530723","name":"华坪县"},{"code":"530724","name":"宁蒗彝族自治县"}]},{"code":"5308","name":"普洱市","children":[{"code":"530802","name":"思茅区"},{"code":"530821","name":"宁洱哈尼族彝族自治县"},{"code":"530822","name":"墨江哈尼族自治县"},{"code":"530823","name":"景东彝族自治县"},{"code":"530824","name":"景谷傣族彝族自治县"},{"code":"530825","name":"镇沅彝族哈尼族拉祜族自治县"},{"code":"530826","name":"江城哈尼族彝族自治县"},{"code":"530827","name":"孟连傣族拉祜族佤族自治县"},{"code":"530828","name":"澜沧拉祜族自治县"},{"code":"530829","name":"西盟佤族自治县"}]},{"code":"5309","name":"临沧市","children":[{"code":"530902","name":"临翔区"},{"code":"530921","name":"凤庆县"},{"code":"530922","name":"云县"},{"code":"530923","name":"永德县"},{"code":"530924","name":"镇康县"},{"code":"530925","name":"双江拉祜族佤族布朗族傣族自治县"},{"code":"530926","name":"耿马傣族佤族自治县"},{"code":"530927","name":"沧源佤族自治县"}]},{"code":"5323","name":"楚雄彝族自治州","children":[{"code":"532301","name":"楚雄市"},{"code":"532322","name":"双柏县"},{"code":"532323","name":"牟定县"},{"code":"532324","name":"南华县"},{"code":"532325","name":"姚安县"},{"code":"532326","name":"大姚县"},{"code":"532327","name":"永仁县"},{"code":"532328","name":"元谋县"},{"code":"532329","name":"武定县"},{"code":"532331","name":"禄丰县"}]},{"code":"5325","name":"红河哈尼族彝族自治州","children":[{"code":"532501","name":"个旧市"},{"code":"532502","name":"开远市"},{"code":"532503","name":"蒙自市"},{"code":"532504","name":"弥勒市"},{"code":"532523","name":"屏边苗族自治县"},{"code":"532524","name":"建水县"},{"code":"532525","name":"石屏县"},{"code":"532527","name":"泸西县"},{"code":"532528","name":"元阳县"},{"code":"532529","name":"红河县"},{"code":"532530","name":"金平苗族瑶族傣族自治县"},{"code":"532531","name":"绿春县"},{"code":"532532","name":"河口瑶族自治县"}]},{"code":"5326","name":"文山壮族苗族自治州","children":[{"code":"532601","name":"文山市"},{"code":"532622","name":"砚山县"},{"code":"532623","name":"西畴县"},{"code":"532624","name":"麻栗坡县"},{"code":"532625","name":"马关县"},{"code":"532626","name":"丘北县"},{"code":"532627","name":"广南县"},{"code":"532628","name":"富宁县"}]},{"code":"5328","name":"西双版纳傣族自治州","children":[{"code":"532801","name":"景洪市"},{"code":"532822","name":"勐海县"},{"code":"532823","name":"勐腊县"}]},{"code":"5329","name":"大理白族自治州","children":[{"code":"532901","name":"大理市"},{"code":"532922","name":"漾濞彝族自治县"},{"code":"532923","name":"祥云县"},{"code":"532924","name":"宾川县"},{"code":"532925","name":"弥渡县"},{"code":"532926","name":"南涧彝族自治县"},{"code":"532927","name":"巍山彝族回族自治县"},{"code":"532928","name":"永平县"},{"code":"532929","name":"云龙县"},{"code":"532930","name":"洱源县"},{"code":"532931","name":"剑川县"},{"code":"532932","name":"鹤庆县"}]},{"code":"5331","name":"德宏傣族景颇族自治州","children":[{"code":"533102","name":"瑞丽市"},{"code":"533103","name":"芒市"},{"code":"533122","name":"梁河县"},{"code":"533123","name":"盈江县"},{"code":"533124","name":"陇川县"}]},{"code":"5333","name":"怒江傈僳族自治州","children":[{"code":"533301","name":"泸水市"},{"code":"533323","name":"福贡县"},{"code":"533324","name":"贡山独龙族怒族自治县"},{"code":"533325","name":"兰坪白族普米族自治县"}]},{"code":"5334","name":"迪庆藏族自治州","children":[{"code":"533401","name":"香格里拉市"},{"code":"533422","name":"德钦县"},{"code":"533423","name":"维西傈僳族自治县"}]}]},{"code":"54","name":"西藏自治区","children":[{"code":"5401","name":"拉萨市","children":[{"code":"540102","name":"城关区"},{"code":"540103","name":"堆龙德庆区"},{"code":"540104","name":"达孜区"},{"code":"540121","name":"林周县"},{"code":"540122","name":"当雄县"},{"code":"540123","name":"尼木县"},{"code":"540124","name":"曲水县"},{"code":"540127","name":"墨竹工卡县"},{"code":"540171","name":"格尔木藏青工业园区"},{"code":"540172","name":"拉萨经济技术开发区"},{"code":"540173","name":"西藏文化旅游创意园区"},{"code":"540174","name":"达孜工业园区"}]},{"code":"5402","name":"日喀则市","children":[{"code":"540202","name":"桑珠孜区"},{"code":"540221","name":"南木林县"},{"code":"540222","name":"江孜县"},{"code":"540223","name":"定日县"},{"code":"540224","name":"萨迦县"},{"code":"540225","name":"拉孜县"},{"code":"540226","name":"昂仁县"},{"code":"540227","name":"谢通门县"},{"code":"540228","name":"白朗县"},{"code":"540229","name":"仁布县"},{"code":"540230","name":"康马县"},{"code":"540231","name":"定结县"},{"code":"540232","name":"仲巴县"},{"code":"540233","name":"亚东县"},{"code":"540234","name":"吉隆县"},{"code":"540235","name":"聂拉木县"},{"code":"540236","name":"萨嘎县"},{"code":"540237","name":"岗巴县"}]},{"code":"5403","name":"昌都市","children":[{"code":"540302","name":"卡若区"},{"code":"540321","name":"江达县"},{"code":"540322","name":"贡觉县"},{"code":"540323","name":"类乌齐县"},{"code":"540324","name":"丁青县"},{"code":"540325","name":"察雅县"},{"code":"540326","name":"八宿县"},{"code":"540327","name":"左贡县"},{"code":"540328","name":"芒康县"},{"code":"540329","name":"洛隆县"},{"code":"540330","name":"边坝县"}]},{"code":"5404","name":"林芝市","children":[{"code":"540402","name":"巴宜区"},{"code":"540421","name":"工布江达县"},{"code":"540422","name":"米林县"},{"code":"540423","name":"墨脱县"},{"code":"540424","name":"波密县"},{"code":"540425","name":"察隅县"},{"code":"540426","name":"朗县"}]},{"code":"5405","name":"山南市","children":[{"code":"540502","name":"乃东区"},{"code":"540521","name":"扎囊县"},{"code":"540522","name":"贡嘎县"},{"code":"540523","name":"桑日县"},{"code":"540524","name":"琼结县"},{"code":"540525","name":"曲松县"},{"code":"540526","name":"措美县"},{"code":"540527","name":"洛扎县"},{"code":"540528","name":"加查县"},{"code":"540529","name":"隆子县"},{"code":"540530","name":"错那县"},{"code":"540531","name":"浪卡子县"}]},{"code":"5406","name":"那曲市","children":[{"code":"540602","name":"色尼区"},{"code":"540621","name":"嘉黎县"},{"code":"540622","name":"比如县"},{"code":"540623","name":"聂荣县"},{"code":"540624","name":"安多县"},{"code":"540625","name":"申扎县"},{"code":"540626","name":"索县"},{"code":"540627","name":"班戈县"},{"code":"540628","name":"巴青县"},{"code":"540629","name":"尼玛县"},{"code":"540630","name":"双湖县"}]},{"code":"5425","name":"阿里地区","children":[{"code":"542521","name":"普兰县"},{"code":"542522","name":"札达县"},{"code":"542523","name":"噶尔县"},{"code":"542524","name":"日土县"},{"code":"542525","name":"革吉县"},{"code":"542526","name":"改则县"},{"code":"542527","name":"措勤县"}]}]},{"code":"61","name":"陕西省","children":[{"code":"6101","name":"西安市","children":[{"code":"610102","name":"新城区"},{"code":"610103","name":"碑林区"},{"code":"610104","name":"莲湖区"},{"code":"610111","name":"灞桥区"},{"code":"610112","name":"未央区"},{"code":"610113","name":"雁塔区"},{"code":"610114","name":"阎良区"},{"code":"610115","name":"临潼区"},{"code":"610116","name":"长安区"},{"code":"610117","name":"高陵区"},{"code":"610118","name":"鄠邑区"},{"code":"610122","name":"蓝田县"},{"code":"610124","name":"周至县"}]},{"code":"6102","name":"铜川市","children":[{"code":"610202","name":"王益区"},{"code":"610203","name":"印台区"},{"code":"610204","name":"耀州区"},{"code":"610222","name":"宜君县"}]},{"code":"6103","name":"宝鸡市","children":[{"code":"610302","name":"渭滨区"},{"code":"610303","name":"金台区"},{"code":"610304","name":"陈仓区"},{"code":"610322","name":"凤翔县"},{"code":"610323","name":"岐山县"},{"code":"610324","name":"扶风县"},{"code":"610326","name":"眉县"},{"code":"610327","name":"陇县"},{"code":"610328","name":"千阳县"},{"code":"610329","name":"麟游县"},{"code":"610330","name":"凤县"},{"code":"610331","name":"太白县"}]},{"code":"6104","name":"咸阳市","children":[{"code":"610402","name":"秦都区"},{"code":"610403","name":"杨陵区"},{"code":"610404","name":"渭城区"},{"code":"610422","name":"三原县"},{"code":"610423","name":"泾阳县"},{"code":"610424","name":"乾县"},{"code":"610425","name":"礼泉县"},{"code":"610426","name":"永寿县"},{"code":"610428","name":"长武县"},{"code":"610429","name":"旬邑县"},{"code":"610430","name":"淳化县"},{"code":"610431","name":"武功县"},{"code":"610481","name":"兴平市"},{"code":"610482","name":"彬州市"}]},{"code":"6105","name":"渭南市","children":[{"code":"610502","name":"临渭区"},{"code":"610503","name":"华州区"},{"code":"610522","name":"潼关县"},{"code":"610523","name":"大荔县"},{"code":"610524","name":"合阳县"},{"code":"610525","name":"澄城县"},{"code":"610526","name":"蒲城县"},{"code":"610527","name":"白水县"},{"code":"610528","name":"富平县"},{"code":"610581","name":"韩城市"},{"code":"610582","name":"华阴市"}]},{"code":"6106","name":"延安市","children":[{"code":"610602","name":"宝塔区"},{"code":"610603","name":"安塞区"},{"code":"610621","name":"延长县"},{"code":"610622","name":"延川县"},{"code":"610625","name":"志丹县"},{"code":"610626","name":"吴起县"},{"code":"610627","name":"甘泉县"},{"code":"610628","name":"富县"},{"code":"610629","name":"洛川县"},{"code":"610630","name":"宜川县"},{"code":"610631","name":"黄龙县"},{"code":"610632","name":"黄陵县"},{"code":"610681","name":"子长市"}]},{"code":"6107","name":"汉中市","children":[{"code":"610702","name":"汉台区"},{"code":"610703","name":"南郑区"},{"code":"610722","name":"城固县"},{"code":"610723","name":"洋县"},{"code":"610724","name":"西乡县"},{"code":"610725","name":"勉县"},{"code":"610726","name":"宁强县"},{"code":"610727","name":"略阳县"},{"code":"610728","name":"镇巴县"},{"code":"610729","name":"留坝县"},{"code":"610730","name":"佛坪县"}]},{"code":"6108","name":"榆林市","children":[{"code":"610802","name":"榆阳区"},{"code":"610803","name":"横山区"},{"code":"610822","name":"府谷县"},{"code":"610824","name":"靖边县"},{"code":"610825","name":"定边县"},{"code":"610826","name":"绥德县"},{"code":"610827","name":"米脂县"},{"code":"610828","name":"佳县"},{"code":"610829","name":"吴堡县"},{"code":"610830","name":"清涧县"},{"code":"610831","name":"子洲县"},{"code":"610881","name":"神木市"}]},{"code":"6109","name":"安康市","children":[{"code":"610902","name":"汉滨区"},{"code":"610921","name":"汉阴县"},{"code":"610922","name":"石泉县"},{"code":"610923","name":"宁陕县"},{"code":"610924","name":"紫阳县"},{"code":"610925","name":"岚皋县"},{"code":"610926","name":"平利县"},{"code":"610927","name":"镇坪县"},{"code":"610928","name":"旬阳县"},{"code":"610929","name":"白河县"}]},{"code":"6110","name":"商洛市","children":[{"code":"611002","name":"商州区"},{"code":"611021","name":"洛南县"},{"code":"611022","name":"丹凤县"},{"code":"611023","name":"商南县"},{"code":"611024","name":"山阳县"},{"code":"611025","name":"镇安县"},{"code":"611026","name":"柞水县"}]}]},{"code":"62","name":"甘肃省","children":[{"code":"6201","name":"兰州市","children":[{"code":"620102","name":"城关区"},{"code":"620103","name":"七里河区"},{"code":"620104","name":"西固区"},{"code":"620105","name":"安宁区"},{"code":"620111","name":"红古区"},{"code":"620121","name":"永登县"},{"code":"620122","name":"皋兰县"},{"code":"620123","name":"榆中县"},{"code":"620171","name":"兰州新区"}]},{"code":"6202","name":"嘉峪关市","children":[{"code":"620201001","name":"雄关街道"},{"code":"620201002","name":"钢城街道"},{"code":"620201100","name":"新城镇"},{"code":"620201101","name":"峪泉镇"},{"code":"620201102","name":"文殊镇"}]},{"code":"6203","name":"金昌市","children":[{"code":"620302","name":"金川区"},{"code":"620321","name":"永昌县"}]},{"code":"6204","name":"白银市","children":[{"code":"620402","name":"白银区"},{"code":"620403","name":"平川区"},{"code":"620421","name":"靖远县"},{"code":"620422","name":"会宁县"},{"code":"620423","name":"景泰县"}]},{"code":"6205","name":"天水市","children":[{"code":"620502","name":"秦州区"},{"code":"620503","name":"麦积区"},{"code":"620521","name":"清水县"},{"code":"620522","name":"秦安县"},{"code":"620523","name":"甘谷县"},{"code":"620524","name":"武山县"},{"code":"620525","name":"张家川回族自治县"}]},{"code":"6206","name":"武威市","children":[{"code":"620602","name":"凉州区"},{"code":"620621","name":"民勤县"},{"code":"620622","name":"古浪县"},{"code":"620623","name":"天祝藏族自治县"}]},{"code":"6207","name":"张掖市","children":[{"code":"620702","name":"甘州区"},{"code":"620721","name":"肃南裕固族自治县"},{"code":"620722","name":"民乐县"},{"code":"620723","name":"临泽县"},{"code":"620724","name":"高台县"},{"code":"620725","name":"山丹县"}]},{"code":"6208","name":"平凉市","children":[{"code":"620802","name":"崆峒区"},{"code":"620821","name":"泾川县"},{"code":"620822","name":"灵台县"},{"code":"620823","name":"崇信县"},{"code":"620825","name":"庄浪县"},{"code":"620826","name":"静宁县"},{"code":"620881","name":"华亭市"}]},{"code":"6209","name":"酒泉市","children":[{"code":"620902","name":"肃州区"},{"code":"620921","name":"金塔县"},{"code":"620922","name":"瓜州县"},{"code":"620923","name":"肃北蒙古族自治县"},{"code":"620924","name":"阿克塞哈萨克族自治县"},{"code":"620981","name":"玉门市"},{"code":"620982","name":"敦煌市"}]},{"code":"6210","name":"庆阳市","children":[{"code":"621002","name":"西峰区"},{"code":"621021","name":"庆城县"},{"code":"621022","name":"环县"},{"code":"621023","name":"华池县"},{"code":"621024","name":"合水县"},{"code":"621025","name":"正宁县"},{"code":"621026","name":"宁县"},{"code":"621027","name":"镇原县"}]},{"code":"6211","name":"定西市","children":[{"code":"621102","name":"安定区"},{"code":"621121","name":"通渭县"},{"code":"621122","name":"陇西县"},{"code":"621123","name":"渭源县"},{"code":"621124","name":"临洮县"},{"code":"621125","name":"漳县"},{"code":"621126","name":"岷县"}]},{"code":"6212","name":"陇南市","children":[{"code":"621202","name":"武都区"},{"code":"621221","name":"成县"},{"code":"621222","name":"文县"},{"code":"621223","name":"宕昌县"},{"code":"621224","name":"康县"},{"code":"621225","name":"西和县"},{"code":"621226","name":"礼县"},{"code":"621227","name":"徽县"},{"code":"621228","name":"两当县"}]},{"code":"6229","name":"临夏回族自治州","children":[{"code":"622901","name":"临夏市"},{"code":"622921","name":"临夏县"},{"code":"622922","name":"康乐县"},{"code":"622923","name":"永靖县"},{"code":"622924","name":"广河县"},{"code":"622925","name":"和政县"},{"code":"622926","name":"东乡族自治县"},{"code":"622927","name":"积石山保安族东乡族撒拉族自治县"}]},{"code":"6230","name":"甘南藏族自治州","children":[{"code":"623001","name":"合作市"},{"code":"623021","name":"临潭县"},{"code":"623022","name":"卓尼县"},{"code":"623023","name":"舟曲县"},{"code":"623024","name":"迭部县"},{"code":"623025","name":"玛曲县"},{"code":"623026","name":"碌曲县"},{"code":"623027","name":"夏河县"}]}]},{"code":"63","name":"青海省","children":[{"code":"6301","name":"西宁市","children":[{"code":"630102","name":"城东区"},{"code":"630103","name":"城中区"},{"code":"630104","name":"城西区"},{"code":"630105","name":"城北区"},{"code":"630106","name":"湟中区"},{"code":"630121","name":"大通回族土族自治县"},{"code":"630123","name":"湟源县"}]},{"code":"6302","name":"海东市","children":[{"code":"630202","name":"乐都区"},{"code":"630203","name":"平安区"},{"code":"630222","name":"民和回族土族自治县"},{"code":"630223","name":"互助土族自治县"},{"code":"630224","name":"化隆回族自治县"},{"code":"630225","name":"循化撒拉族自治县"}]},{"code":"6322","name":"海北藏族自治州","children":[{"code":"632221","name":"门源回族自治县"},{"code":"632222","name":"祁连县"},{"code":"632223","name":"海晏县"},{"code":"632224","name":"刚察县"}]},{"code":"6323","name":"黄南藏族自治州","children":[{"code":"632321","name":"同仁县"},{"code":"632322","name":"尖扎县"},{"code":"632323","name":"泽库县"},{"code":"632324","name":"河南蒙古族自治县"}]},{"code":"6325","name":"海南藏族自治州","children":[{"code":"632521","name":"共和县"},{"code":"632522","name":"同德县"},{"code":"632523","name":"贵德县"},{"code":"632524","name":"兴海县"},{"code":"632525","name":"贵南县"}]},{"code":"6326","name":"果洛藏族自治州","children":[{"code":"632621","name":"玛沁县"},{"code":"632622","name":"班玛县"},{"code":"632623","name":"甘德县"},{"code":"632624","name":"达日县"},{"code":"632625","name":"久治县"},{"code":"632626","name":"玛多县"}]},{"code":"6327","name":"玉树藏族自治州","children":[{"code":"632701","name":"玉树市"},{"code":"632722","name":"杂多县"},{"code":"632723","name":"称多县"},{"code":"632724","name":"治多县"},{"code":"632725","name":"囊谦县"},{"code":"632726","name":"曲麻莱县"}]},{"code":"6328","name":"海西蒙古族藏族自治州","children":[{"code":"632801","name":"格尔木市"},{"code":"632802","name":"德令哈市"},{"code":"632803","name":"茫崖市"},{"code":"632821","name":"乌兰县"},{"code":"632822","name":"都兰县"},{"code":"632823","name":"天峻县"},{"code":"632857","name":"大柴旦行政委员会"}]}]},{"code":"64","name":"宁夏回族自治区","children":[{"code":"6401","name":"银川市","children":[{"code":"640104","name":"兴庆区"},{"code":"640105","name":"西夏区"},{"code":"640106","name":"金凤区"},{"code":"640121","name":"永宁县"},{"code":"640122","name":"贺兰县"},{"code":"640181","name":"灵武市"}]},{"code":"6402","name":"石嘴山市","children":[{"code":"640202","name":"大武口区"},{"code":"640205","name":"惠农区"},{"code":"640221","name":"平罗县"}]},{"code":"6403","name":"吴忠市","children":[{"code":"640302","name":"利通区"},{"code":"640303","name":"红寺堡区"},{"code":"640323","name":"盐池县"},{"code":"640324","name":"同心县"},{"code":"640381","name":"青铜峡市"}]},{"code":"6404","name":"固原市","children":[{"code":"640402","name":"原州区"},{"code":"640422","name":"西吉县"},{"code":"640423","name":"隆德县"},{"code":"640424","name":"泾源县"},{"code":"640425","name":"彭阳县"}]},{"code":"6405","name":"中卫市","children":[{"code":"640502","name":"沙坡头区"},{"code":"640521","name":"中宁县"},{"code":"640522","name":"海原县"}]}]},{"code":"65","name":"新疆维吾尔自治区","children":[{"code":"6501","name":"乌鲁木齐市","children":[{"code":"650102","name":"天山区"},{"code":"650103","name":"沙依巴克区"},{"code":"650104","name":"新市区"},{"code":"650105","name":"水磨沟区"},{"code":"650106","name":"头屯河区"},{"code":"650107","name":"达坂城区"},{"code":"650109","name":"米东区"},{"code":"650121","name":"乌鲁木齐县"}]},{"code":"6502","name":"克拉玛依市","children":[{"code":"650202","name":"独山子区"},{"code":"650203","name":"克拉玛依区"},{"code":"650204","name":"白碱滩区"},{"code":"650205","name":"乌尔禾区"}]},{"code":"6504","name":"吐鲁番市","children":[{"code":"650402","name":"高昌区"},{"code":"650421","name":"鄯善县"},{"code":"650422","name":"托克逊县"}]},{"code":"6505","name":"哈密市","children":[{"code":"650502","name":"伊州区"},{"code":"650521","name":"巴里坤哈萨克自治县"},{"code":"650522","name":"伊吾县"}]},{"code":"6523","name":"昌吉回族自治州","children":[{"code":"652301","name":"昌吉市"},{"code":"652302","name":"阜康市"},{"code":"652323","name":"呼图壁县"},{"code":"652324","name":"玛纳斯县"},{"code":"652325","name":"奇台县"},{"code":"652327","name":"吉木萨尔县"},{"code":"652328","name":"木垒哈萨克自治县"}]},{"code":"6527","name":"博尔塔拉蒙古自治州","children":[{"code":"652701","name":"博乐市"},{"code":"652702","name":"阿拉山口市"},{"code":"652722","name":"精河县"},{"code":"652723","name":"温泉县"}]},{"code":"6528","name":"巴音郭楞蒙古自治州","children":[{"code":"652801","name":"库尔勒市"},{"code":"652822","name":"轮台县"},{"code":"652823","name":"尉犁县"},{"code":"652824","name":"若羌县"},{"code":"652825","name":"且末县"},{"code":"652826","name":"焉耆回族自治县"},{"code":"652827","name":"和静县"},{"code":"652828","name":"和硕县"},{"code":"652829","name":"博湖县"},{"code":"652871","name":"库尔勒经济技术开发区"}]},{"code":"6529","name":"阿克苏地区","children":[{"code":"652901","name":"阿克苏市"},{"code":"652902","name":"库车市"},{"code":"652922","name":"温宿县"},{"code":"652924","name":"沙雅县"},{"code":"652925","name":"新和县"},{"code":"652926","name":"拜城县"},{"code":"652927","name":"乌什县"},{"code":"652928","name":"阿瓦提县"},{"code":"652929","name":"柯坪县"}]},{"code":"6530","name":"克孜勒苏柯尔克孜自治州","children":[{"code":"653001","name":"阿图什市"},{"code":"653022","name":"阿克陶县"},{"code":"653023","name":"阿合奇县"},{"code":"653024","name":"乌恰县"}]},{"code":"6531","name":"喀什地区","children":[{"code":"653101","name":"喀什市"},{"code":"653121","name":"疏附县"},{"code":"653122","name":"疏勒县"},{"code":"653123","name":"英吉沙县"},{"code":"653124","name":"泽普县"},{"code":"653125","name":"莎车县"},{"code":"653126","name":"叶城县"},{"code":"653127","name":"麦盖提县"},{"code":"653128","name":"岳普湖县"},{"code":"653129","name":"伽师县"},{"code":"653130","name":"巴楚县"},{"code":"653131","name":"塔什库尔干塔吉克自治县"}]},{"code":"6532","name":"和田地区","children":[{"code":"653201","name":"和田市"},{"code":"653221","name":"和田县"},{"code":"653222","name":"墨玉县"},{"code":"653223","name":"皮山县"},{"code":"653224","name":"洛浦县"},{"code":"653225","name":"策勒县"},{"code":"653226","name":"于田县"},{"code":"653227","name":"民丰县"}]},{"code":"6540","name":"伊犁哈萨克自治州","children":[{"code":"654002","name":"伊宁市"},{"code":"654003","name":"奎屯市"},{"code":"654004","name":"霍尔果斯市"},{"code":"654021","name":"伊宁县"},{"code":"654022","name":"察布查尔锡伯自治县"},{"code":"654023","name":"霍城县"},{"code":"654024","name":"巩留县"},{"code":"654025","name":"新源县"},{"code":"654026","name":"昭苏县"},{"code":"654027","name":"特克斯县"},{"code":"654028","name":"尼勒克县"}]},{"code":"6542","name":"塔城地区","children":[{"code":"654201","name":"塔城市"},{"code":"654202","name":"乌苏市"},{"code":"654221","name":"额敏县"},{"code":"654223","name":"沙湾县"},{"code":"654224","name":"托里县"},{"code":"654225","name":"裕民县"},{"code":"654226","name":"和布克赛尔蒙古自治县"}]},{"code":"6543","name":"阿勒泰地区","children":[{"code":"654301","name":"阿勒泰市"},{"code":"654321","name":"布尔津县"},{"code":"654322","name":"富蕴县"},{"code":"654323","name":"福海县"},{"code":"654324","name":"哈巴河县"},{"code":"654325","name":"青河县"},{"code":"654326","name":"吉木乃县"}]},{"code":"6590","name":"自治区直辖县级行政区划","children":[{"code":"659001","name":"石河子市"},{"code":"659002","name":"阿拉尔市"},{"code":"659003","name":"图木舒克市"},{"code":"659004","name":"五家渠市"},{"code":"659005","name":"北屯市"},{"code":"659006","name":"铁门关市"},{"code":"659007","name":"双河市"},{"code":"659008","name":"可克达拉市"},{"code":"659009","name":"昆玉市"},{"code":"659010","name":"胡杨河市"}]}]}] \ No newline at end of file diff --git a/backend/dvadmin/system/views/__init__.py b/backend/dvadmin/system/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/system/views/api_white_list.py b/backend/dvadmin/system/views/api_white_list.py new file mode 100644 index 0000000..db1c117 --- /dev/null +++ b/backend/dvadmin/system/views/api_white_list.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2022/1/1 001 9:34 +@Remark: +""" +from dvadmin.system.models import ApiWhiteList +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class ApiWhiteListSerializer(CustomModelSerializer): + """ + 接口白名单-序列化器 + """ + + class Meta: + model = ApiWhiteList + fields = "__all__" + read_only_fields = ["id"] + + + + + +class ApiWhiteListViewSet(CustomModelViewSet): + """ + 接口白名单 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = ApiWhiteList.objects.all() + serializer_class = ApiWhiteListSerializer + # permission_classes = [] diff --git a/backend/dvadmin/system/views/area.py b/backend/dvadmin/system/views/area.py new file mode 100644 index 0000000..7a19eb0 --- /dev/null +++ b/backend/dvadmin/system/views/area.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +import pypinyin +from django.db.models import Q +from rest_framework import serializers + +from dvadmin.system.models import Area +from dvadmin.utils.field_permission import FieldPermissionMixin +from dvadmin.utils.json_response import SuccessResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class AreaSerializer(CustomModelSerializer): + """ + 地区-序列化器 + """ + pcode_count = serializers.SerializerMethodField(read_only=True) + hasChild = serializers.SerializerMethodField() + pcode_info = serializers.SerializerMethodField() + + def get_pcode_info(self, instance): + pcode = Area.objects.filter(code=instance.pcode_id).values("name", "code") + return pcode + + def get_pcode_count(self, instance: Area): + return Area.objects.filter(pcode=instance).count() + + def get_hasChild(self, instance): + hasChild = Area.objects.filter(pcode=instance.code) + if hasChild: + return True + return False + + class Meta: + model = Area + fields = "__all__" + read_only_fields = ["id"] + + +class AreaCreateUpdateSerializer(CustomModelSerializer): + """ + 地区管理 创建/更新时的列化器 + """ + + def to_internal_value(self, data): + pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(data["name"], style=pypinyin.NORMAL)]) + data["level"] = 1 + data["pinyin"] = pinyin + data["initials"] = pinyin[0].upper() if pinyin else "#" + pcode = data["pcode"] if 'pcode' in data else None + if pcode: + pcode = Area.objects.get(pk=pcode) + data["pcode"] = pcode.code + data["level"] = pcode.level + 1 + return super().to_internal_value(data) + + class Meta: + model = Area + fields = '__all__' + + +class AreaViewSet(CustomModelViewSet, FieldPermissionMixin): + """ + 地区管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = Area.objects.all() + serializer_class = AreaSerializer + create_serializer_class = AreaCreateUpdateSerializer + update_serializer_class = AreaCreateUpdateSerializer + extra_filter_class = [] + + def list(self, request, *args, **kwargs): + self.request.query_params._mutable = True + params = self.request.query_params + known_params = {'page', 'limit', 'pcode'} + # 使用集合操作检查是否有未知参数 + other_params_exist = any(param not in known_params for param in params) + if other_params_exist: + queryset = self.queryset.filter(enable=True) + else: + pcode = params.get('pcode', None) + params['limit'] = 999 + if params and pcode: + queryset = self.queryset.filter(enable=True, pcode=pcode) + else: + queryset = self.queryset.filter(enable=True, level=1) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, request=request) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") diff --git a/backend/dvadmin/system/views/clause.py b/backend/dvadmin/system/views/clause.py new file mode 100644 index 0000000..81a766c --- /dev/null +++ b/backend/dvadmin/system/views/clause.py @@ -0,0 +1,23 @@ +from rest_framework.views import APIView +from django.shortcuts import render + + +class PrivacyView(APIView): + """ + 后台隐私政策 + """ + permission_classes = [] + + def get(self, request, *args, **kwargs): + return render(request, 'privacy.html') + + + +class TermsServiceView(APIView): + """ + 后台服务条款 + """ + permission_classes = [] + + def get(self, request, *args, **kwargs): + return render(request, 'terms_service.html') diff --git a/backend/dvadmin/system/views/dept.py b/backend/dvadmin/system/views/dept.py new file mode 100644 index 0000000..de48b0d --- /dev/null +++ b/backend/dvadmin/system/views/dept.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- + +""" +@author: H0nGzA1 +@contact: QQ:2505811377 +@Remark: 部门管理 +""" +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users +from dvadmin.utils.filters import DataLevelPermissionsFilter +from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class DeptSerializer(CustomModelSerializer): + """ + 部门-序列化器 + """ + parent_name = serializers.CharField(read_only=True, source='parent.name') + status_label = serializers.SerializerMethodField() + has_children = serializers.SerializerMethodField() + hasChild = serializers.SerializerMethodField() + + dept_user_count = serializers.SerializerMethodField() + + def get_dept_user_count(self, obj: Dept): + return Users.objects.filter(dept=obj).count() + + def get_hasChild(self, instance): + hasChild = Dept.objects.filter(parent=instance.id) + if hasChild: + return True + return False + + def get_status_label(self, obj: Dept): + if obj.status: + return "启用" + return "禁用" + + def get_has_children(self, obj: Dept): + return Dept.objects.filter(parent_id=obj.id).count() + + class Meta: + model = Dept + fields = '__all__' + read_only_fields = ["id"] + + +class DeptImportSerializer(CustomModelSerializer): + """ + 部门-导入-序列化器 + """ + + class Meta: + model = Dept + fields = '__all__' + read_only_fields = ["id"] + + +class DeptCreateUpdateSerializer(CustomModelSerializer): + """ + 部门管理 创建/更新时的列化器 + """ + + def create(self, validated_data): + value = validated_data.get('parent', None) + if value is None: + validated_data['parent'] = self.request.user.dept + dept_obj = Dept.objects.filter(parent=self.request.user.dept).order_by('-sort').first() + last_sort = dept_obj.sort if dept_obj else 0 + validated_data['sort'] = last_sort + 1 + instance = super().create(validated_data) + instance.dept_belong_id = instance.id + instance.save() + return instance + + class Meta: + model = Dept + fields = '__all__' + + +class DeptViewSet(CustomModelViewSet): + """ + 部门管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = Dept.objects.all() + serializer_class = DeptSerializer + create_serializer_class = DeptCreateUpdateSerializer + update_serializer_class = DeptCreateUpdateSerializer + filter_fields = ['name', 'id', 'parent'] + search_fields = [] + # extra_filter_class = [] + import_serializer_class = DeptImportSerializer + import_field_dict = { + "name": "部门名称", + "key": "部门标识", + } + + def list(self, request, *args, **kwargs): + # 如果懒加载,则只返回父级 + request.query_params._mutable = True + params = request.query_params + parent = params.get('parent', None) + page = params.get('page', None) + limit = params.get('limit', None) + if page: + del params['page'] + if limit: + del params['limit'] + if params and parent: + queryset = self.queryset.filter(status=True, parent=parent) + else: + queryset = self.queryset.filter(status=True) + queryset = self.filter_queryset(queryset) + serializer = DeptSerializer(queryset, many=True, request=request) + data = serializer.data + return SuccessResponse(data=data) + + @action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated]) + def all_dept(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent') + return DetailResponse(data=data, msg="获取成功") + + @action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated]) + def move_up(self, request): + """部门上移""" + dept_id = request.data.get('dept_id') + try: + dept = Dept.objects.get(id=dept_id) + except Dept.DoesNotExist: + return ErrorResponse(msg="部门不存在") + previous_menu = Dept.objects.filter(sort__lt=dept.sort, parent=dept.parent).order_by('-sort').first() + if previous_menu: + previous_menu.sort, dept.sort = dept.sort, previous_menu.sort + previous_menu.save() + dept.save() + return SuccessResponse(data=[], msg="上移成功") + + @action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated]) + def move_down(self, request): + """部门下移""" + dept_id = request.data['dept_id'] + try: + dept = Dept.objects.get(id=dept_id) + except Dept.DoesNotExist: + return ErrorResponse(msg="部门不存在") + next_menu = Dept.objects.filter(sort__gt=dept.sort, parent=dept.parent).order_by('sort').first() + if next_menu: + next_menu.sort, dept.sort = dept.sort, next_menu.sort + next_menu.save() + dept.save() + return SuccessResponse(data=[], msg="下移成功") + + @action(methods=['GET'], detail=False, permission_classes=[]) + def dept_info(self, request): + """部门信息""" + def inner(did, li): + sub = Dept.objects.filter(parent_id=did) + if not sub.exists(): + return li + for i in sub: + li.append(i.pk) + inner(i, li) + return li + dept_id = request.query_params.get('dept_id') + show_all = request.query_params.get('show_all') + if dept_id is None: + return ErrorResponse(msg="部门不存在") + if not show_all: + show_all = 0 + if int(show_all): # 递归当前部门下的所有部门,查询用户 + all_did = [dept_id] + inner(dept_id, all_did) + users = Users.objects.filter(dept_id__in=all_did) + else: + if dept_id != '': + users = Users.objects.filter(dept_id=dept_id) + else: + users = Users.objects.none() + dept_obj = Dept.objects.get(id=dept_id) if dept_id != '' else None + sub_dept = Dept.objects.filter(parent_id=dept_obj.pk) if dept_id != '' else [] + data = { + 'dept_name': dept_obj and dept_obj.name, + 'dept_user': users.count(), + 'owner': dept_obj and dept_obj.owner, + 'description': dept_obj and dept_obj.description, + 'gender': { + 'male': users.filter(gender=1).count(), + 'female': users.filter(gender=2).count(), + 'unknown': users.filter(gender=0).count(), + }, + 'sub_dept_map': [] + } + for dept in sub_dept: + all_did = [dept.pk] + inner(dept.pk, all_did) + sub_data = { + 'name': dept.name, + 'count': Users.objects.filter(dept_id__in=all_did).count() + } + data['sub_dept_map'].append(sub_data) + return SuccessResponse(data) diff --git a/backend/dvadmin/system/views/dictionary.py b/backend/dvadmin/system/views/dictionary.py new file mode 100644 index 0000000..46c2a9c --- /dev/null +++ b/backend/dvadmin/system/views/dictionary.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/3 003 0:30 +@Remark: 字典管理 +""" +from rest_framework import serializers +from rest_framework.views import APIView + +from application import dispatch +from dvadmin.system.models import Dictionary +from dvadmin.utils.json_response import SuccessResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class DictionarySerializer(CustomModelSerializer): + """ + 字典-序列化器 + """ + + class Meta: + model = Dictionary + fields = "__all__" + read_only_fields = ["id"] + + + + + +class DictionaryCreateUpdateSerializer(CustomModelSerializer): + """ + 字典管理 创建/更新时的列化器 + """ + value = serializers.CharField(max_length=100) + + def validate_value(self, value): + """ + 在父级的字典编号验证重复性 + """ + initial_data = self.initial_data + parent = initial_data.get('parent',None) + if parent is None: + unique = Dictionary.objects.filter(value=value).exists() + if unique: + raise serializers.ValidationError("字典编号不能重复") + return value + + class Meta: + model = Dictionary + fields = '__all__' + + +class DictionaryViewSet(CustomModelViewSet): + """ + 字典管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = Dictionary.objects.all() + serializer_class = DictionarySerializer + create_serializer_class = DictionaryCreateUpdateSerializer + extra_filter_class = [] + search_fields = ['label'] + + def get_queryset(self): + if self.action =='list': + params = self.request.query_params + parent = params.get('parent', None) + if params: + if parent: + queryset = self.queryset.filter(parent=parent) + else: + queryset = self.queryset.filter(parent__isnull=True) + else: + queryset = self.queryset.filter(parent__isnull=True) + return queryset + else: + return self.queryset + + +class InitDictionaryViewSet(APIView): + """ + 获取初始化配置 + """ + authentication_classes = [] + permission_classes = [] + queryset = Dictionary.objects.all() + + def get(self, request): + dictionary_key = self.request.query_params.get('dictionary_key') + if dictionary_key: + if dictionary_key == 'all': + data = [ele for ele in dispatch.get_dictionary_config().values()] + if not data: + dispatch.refresh_dictionary() + data = [ele for ele in dispatch.get_dictionary_config().values()] + else: + data = self.queryset.filter(parent__value=dictionary_key, status=True).values('label', 'value', 'type', + 'color') + return SuccessResponse(data=data, msg="获取成功") + return SuccessResponse(data=[], msg="获取成功") diff --git a/backend/dvadmin/system/views/download_center.py b/backend/dvadmin/system/views/download_center.py new file mode 100644 index 0000000..4e6b061 --- /dev/null +++ b/backend/dvadmin/system/views/download_center.py @@ -0,0 +1,54 @@ +from rest_framework import serializers +from django.conf import settings +from django_filters.rest_framework import FilterSet, CharFilter + +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet +from dvadmin.system.models import DownloadCenter + + +class DownloadCenterSerializer(CustomModelSerializer): + url = serializers.SerializerMethodField(read_only=True) + + def get_url(self, instance): + if self.request.query_params.get('prefix'): + if settings.ENVIRONMENT in ['local']: + prefix = 'http://127.0.0.1:8000' + elif settings.ENVIRONMENT in ['test']: + prefix = 'http://{host}/api'.format(host=self.request.get_host()) + else: + prefix = 'https://{host}/api'.format(host=self.request.get_host()) + return (f'{prefix}/media/{str(instance.url)}') + return f'media/{str(instance.url)}' + + class Meta: + model = DownloadCenter + fields = "__all__" + read_only_fields = ["id"] + + +class DownloadCenterFilterSet(FilterSet): + task_name = CharFilter(field_name='task_name', lookup_expr='icontains') + file_name = CharFilter(field_name='file_name', lookup_expr='icontains') + + class Meta: + model = DownloadCenter + fields = ['task_status', 'task_name', 'file_name'] + + +class DownloadCenterViewSet(CustomModelViewSet): + queryset = DownloadCenter.objects.all() + serializer_class = DownloadCenterSerializer + filter_class = DownloadCenterFilterSet + permission_classes = [] + extra_filter_class = [] + + def get_queryset(self): + # 判断是否是 Swagger 文档生成阶段,防止报错 + if getattr(self, 'swagger_fake_view', False): + return self.queryset.model.objects.none() + + # 正常请求下的逻辑 + if self.request.user.is_superuser: + return super().get_queryset() + return super().get_queryset().filter(creator=self.request.user) diff --git a/backend/dvadmin/system/views/file_list.py b/backend/dvadmin/system/views/file_list.py new file mode 100644 index 0000000..8a43495 --- /dev/null +++ b/backend/dvadmin/system/views/file_list.py @@ -0,0 +1,116 @@ +import hashlib +import mimetypes + +import django_filters +from django.conf import settings +from django.db import connection +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from application import dispatch +from dvadmin.system.models import FileList +from dvadmin.utils.json_response import DetailResponse, SuccessResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class FileSerializer(CustomModelSerializer): + url = serializers.SerializerMethodField(read_only=True) + + def get_url(self, instance): + if self.request.query_params.get('prefix'): + if settings.ENVIRONMENT in ['local']: + prefix = 'http://127.0.0.1:8000' + elif settings.ENVIRONMENT in ['test']: + prefix = 'http://{host}/api'.format(host=self.request.get_host()) + else: + prefix = 'https://{host}/api'.format(host=self.request.get_host()) + if instance.file_url: + return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}" + return (f'{prefix}/media/{str(instance.url)}') + return instance.file_url or (f'media/{str(instance.url)}') + + class Meta: + model = FileList + fields = "__all__" + + def create(self, validated_data): + file = self.initial_data.get('file') + file_size = file.size + validated_data['name'] = str(file) + validated_data['size'] = file_size + md5 = hashlib.md5() + for chunk in file.chunks(): + md5.update(chunk) + validated_data['md5sum'] = md5.hexdigest() + validated_data['engine'] = 'rustfs' + validated_data['mime_type'] = file.content_type + ft = {'image':0,'video':1,'audio':2}.get(file.content_type.split('/')[0], None) + validated_data['file_type'] = 3 if ft is None else ft + + # 上传到 RustFS + from dvadmin.utils.rustfs_storage import rustfs_upload_file + file_path = rustfs_upload_file(file, file_name=validated_data['name']) + if file_path: + validated_data['file_url'] = file_path + else: + raise ValueError("文件上传失败") + + # 审计字段 + try: + request_user = self.request.user + validated_data['dept_belong_id'] = request_user.dept.id + validated_data['creator'] = request_user.id + validated_data['modifier'] = request_user.id + except: + pass + return super().create(validated_data) + + +class FileAllSerializer(CustomModelSerializer): + + class Meta: + model = FileList + fields = ['id', 'name'] + + +class FileFilter(django_filters.FilterSet): + name = django_filters.CharFilter(field_name="name", lookup_expr="icontains", help_text="文件名") + mime_type = django_filters.CharFilter(field_name="mime_type", lookup_expr="icontains", help_text="文件类型") + + class Meta: + model = FileList + fields = ['name', 'mime_type', 'upload_method', 'file_type'] + + +class FileViewSet(CustomModelViewSet): + """ + 文件管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = FileList.objects.all() + serializer_class = FileSerializer + filter_class = FileFilter + permission_classes = [IsAuthenticated] + + @action(methods=['GET'], detail=False) + def get_all(self, request): + data1 = self.get_serializer(self.get_queryset(), many=True).data + data2 = [] + if dispatch.is_tenants_mode(): + from django_tenants.utils import schema_context + with schema_context('public'): + data2 = self.get_serializer(FileList.objects.all(), many=True).data + return DetailResponse(data=data2+data1) + + def list(self, request, *args, **kwargs): + if self.request.query_params.get('system', 'False') == 'True' and dispatch.is_tenants_mode(): + from django_tenants.utils import schema_context + with schema_context('public'): + return super().list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) diff --git a/backend/dvadmin/system/views/login.py b/backend/dvadmin/system/views/login.py new file mode 100644 index 0000000..c51ed64 --- /dev/null +++ b/backend/dvadmin/system/views/login.py @@ -0,0 +1,389 @@ +import base64 +import hashlib +import logging +from datetime import datetime, timedelta +from captcha.views import CaptchaStore, captcha_image +from django.contrib import auth +from django.contrib.auth import login +from django.contrib.auth.hashers import check_password, make_password +from django.db.models import Q +from django.shortcuts import redirect +from django.utils.translation import gettext_lazy as _ +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.views import APIView +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from rest_framework_simplejwt.views import TokenObtainPairView +from django.conf import settings +from application import dispatch +from dvadmin.system.models import Users, Dept +from dvadmin.utils.json_response import ErrorResponse, DetailResponse +from dvadmin.utils.request_util import save_login_log +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.validator import CustomValidationError + +logger = logging.getLogger(__name__) + + +def _is_supplier_portal_role_key(role_key: str) -> bool: + """供应商端登录:允许 role.key 为 supplier 或 supplier_*(如 supplier_quote)。""" + if not role_key: + return False + return role_key == "supplier" or role_key.startswith("supplier_") + + +class CaptchaView(APIView): + authentication_classes = [] + permission_classes = [] + + @swagger_auto_schema( + responses={"200": openapi.Response("获取成功")}, + security=[], + operation_id="captcha-get", + operation_description="验证码获取", + ) + def get(self, request): + data = {} + if dispatch.get_system_config_values("base.captcha_state"): + hashkey = CaptchaStore.generate_key() + id = CaptchaStore.objects.filter(hashkey=hashkey).first().id + imgage = captcha_image(request, hashkey) + # 将图片转换为base64 + image_base = base64.b64encode(imgage.content) + data = { + "key": id, + "image_base": "data:image/png;base64," + image_base.decode("utf-8"), + } + return DetailResponse(data=data) + + +class LoginSerializer(TokenObtainPairSerializer): + """ + 登录的序列化器: + 重写djangorestframework-simplejwt的序列化器 + """ + captcha = serializers.CharField( + max_length=6, required=False, allow_null=True, allow_blank=True + ) + + class Meta: + model = Users + fields = "__all__" + read_only_fields = ["id"] + + default_error_messages = {"no_active_account": _("账号/密码错误")} + + def validate(self, attrs): + captcha = self.initial_data.get("captcha", None) + if dispatch.get_system_config_values("base.captcha_state"): + if captcha is None: + raise CustomValidationError("验证码不能为空") + self.image_code = CaptchaStore.objects.filter( + id=self.initial_data["captchaKey"] + ).first() + five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) + if self.image_code and five_minute_ago > self.image_code.expiration: + self.image_code and self.image_code.delete() + raise CustomValidationError("验证码过期") + else: + if self.image_code and ( + self.image_code.response == captcha + or self.image_code.challenge == captcha + ): + self.image_code and self.image_code.delete() + else: + self.image_code and self.image_code.delete() + raise CustomValidationError("图片验证码错误") + try: + user = Users.objects.get( + Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username'])) + except Users.DoesNotExist: + raise CustomValidationError("您登录的账号不存在") + except Users.MultipleObjectsReturned: + raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性") + if not user.is_active: + raise CustomValidationError("账号已被锁定,联系管理员解锁") + # 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误 + attrs['username'] = user.username + try: + data = super().validate(attrs) + except Exception as e: + user.login_error_count += 1 + if user.login_error_count >= 5: + user.is_active = False + user.save() + raise CustomValidationError("账号已被锁定,联系管理员解锁") + user.save() + count = 5 - user.login_error_count + raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~") + data["username"] = self.user.username + data["name"] = self.user.name + data["userId"] = self.user.id + data["avatar"] = self.user.avatar + data['user_type'] = self.user.user_type + data['pwd_change_count'] = self.user.pwd_change_count + dept = Dept.objects.filter(id=self.user.dept_id).values("id", "name").first() if self.user.dept_id else None + if dept: + data['dept_info'] = { + 'dept_id': dept["id"], + 'dept_name': dept["name"], + } + role = getattr(self.user, 'role', None) + if role: + data['role_info'] = role.values('id', 'name', 'key') + request = self.context.get("request") + if request is not None: + request.user = self.user + try: + # 登录日志失败不应影响登录成功结果,更不能继续累计错误次数。 + save_login_log(request=request) + except Exception: + logger.exception("记录登录日志失败: %s", self.user.username) + user.login_error_count = 0 + user.save() + return {"code": 2000, "msg": "请求成功", "data": data} + + +class SupplierLoginSerializer(TokenObtainPairSerializer): + """ + 供应商登录的序列化器 + 复用 LoginSerializer 的验证逻辑,额外校验 role.key 为 supplier 或 supplier_*(如 supplier_quote) + """ + captcha = serializers.CharField( + max_length=6, required=False, allow_null=True, allow_blank=True + ) + + class Meta: + model = Users + fields = "__all__" + read_only_fields = ["id"] + + default_error_messages = {"no_active_account": _("账号/密码错误")} + + def validate(self, attrs): + captcha = self.initial_data.get("captcha", None) + if dispatch.get_system_config_values("base.captcha_state"): + if captcha is None: + raise CustomValidationError("验证码不能为空") + self.image_code = CaptchaStore.objects.filter( + id=self.initial_data["captchaKey"] + ).first() + five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) + if self.image_code and five_minute_ago > self.image_code.expiration: + self.image_code and self.image_code.delete() + raise CustomValidationError("验证码过期") + else: + if self.image_code and ( + self.image_code.response == captcha + or self.image_code.challenge == captcha + ): + self.image_code and self.image_code.delete() + else: + self.image_code and self.image_code.delete() + raise CustomValidationError("图片验证码错误") + try: + user = Users.objects.get( + Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username'])) + except Users.DoesNotExist: + raise CustomValidationError("您登录的账号不存在") + except Users.MultipleObjectsReturned: + raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性") + if not user.is_active: + raise CustomValidationError("账号已被锁定,联系管理员解锁") + # 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误 + attrs['username'] = user.username + + # 校验用户角色是否为供应商(含 supplier_quote 等 supplier_*) + user_roles = user.role.all() + role_keys = [r.key for r in user_roles] + if not any(_is_supplier_portal_role_key(k) for k in role_keys): + raise CustomValidationError("该账号不是供应商账号,请使用采购方入口登录") + + try: + data = super().validate(attrs) + except Exception as e: + user.login_error_count += 1 + if user.login_error_count >= 5: + user.is_active = False + user.save() + raise CustomValidationError("账号已被锁定,联系管理员解锁") + user.save() + count = 5 - user.login_error_count + raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~") + data["username"] = self.user.username + data["name"] = self.user.name + data["userId"] = self.user.id + data["avatar"] = self.user.avatar + data['user_type'] = self.user.user_type + data['pwd_change_count'] = self.user.pwd_change_count + dept = Dept.objects.filter(id=self.user.dept_id).values("id", "name").first() if self.user.dept_id else None + if dept: + data['dept_info'] = { + 'dept_id': dept["id"], + 'dept_name': dept["name"], + } + role = getattr(self.user, 'role', None) + if role: + data['role_info'] = role.values('id', 'name', 'key') + request = self.context.get("request") + if request is not None: + request.user = self.user + try: + save_login_log(request=request) + except Exception: + logger.exception("记录登录日志失败: %s", self.user.username) + user.login_error_count = 0 + user.save() + return {"code": 2000, "msg": "请求成功", "data": data} + + +class LoginView(TokenObtainPairView): + """ + 登录接口 + """ + serializer_class = LoginSerializer + permission_classes = [] + + +class SupplierLoginView(TokenObtainPairView): + """ + 供应商登录接口 + """ + serializer_class = SupplierLoginSerializer + permission_classes = [] + + # def post(self, request, *args, **kwargs): + # # username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱 + # username = request.data.get('username',None) + # if username is None: + # return ErrorResponse(msg="参数错误") + # password = request.data.get('password',None) + # if password is None: + # return ErrorResponse(msg="参数错误") + # captcha = request.data.get('captcha',None) + # if captcha is None: + # return ErrorResponse(msg="参数错误") + # captchaKey = request.data.get('captchaKey',None) + # if captchaKey is None: + # return ErrorResponse(msg="参数错误") + # if dispatch.get_system_config_values("base.captcha_state"): + # if captcha is None: + # raise CustomValidationError("验证码不能为空") + # self.image_code = CaptchaStore.objects.filter( + # id=captchaKey + # ).first() + # five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) + # if self.image_code and five_minute_ago > self.image_code.expiration: + # self.image_code and self.image_code.delete() + # raise CustomValidationError("验证码过期") + # else: + # if self.image_code and ( + # self.image_code.response == captcha + # or self.image_code.challenge == captcha + # ): + # self.image_code and self.image_code.delete() + # else: + # self.image_code and self.image_code.delete() + # raise CustomValidationError("图片验证码错误") + # try: + # # 手动通过 user 签发 jwt-token + # user = Users.objects.get(username=username) + # except: + # return DetailResponse(msg='该账号未注册') + # # 获得用户后,校验密码并签发token + # print(make_password(password),user.password) + # if check_password(make_password(password),user.password): + # return DetailResponse(msg='密码错误') + # result = { + # "name":user.name, + # "userId":user.id, + # "avatar":user.avatar, + # } + # dept = getattr(user, 'dept', None) + # if dept: + # result['dept_info'] = { + # 'dept_id': dept.id, + # 'dept_name': dept.name, + # 'dept_key': dept.key + # } + # role = getattr(user, 'role', None) + # if role: + # result['role_info'] = role.values('id', 'name', 'key') + # refresh = LoginSerializer.get_token(user) + # result["refresh"] = str(refresh) + # result["access"] = str(refresh.access_token) + # # 记录登录日志 + # request.user = user + # save_login_log(request=request) + # return DetailResponse(data=result,msg="获取成功") + + +class LoginTokenSerializer(TokenObtainPairSerializer): + """ + 登录的序列化器: + """ + + class Meta: + model = Users + fields = "__all__" + read_only_fields = ["id"] + + default_error_messages = {"no_active_account": _("账号/密码不正确")} + + def validate(self, attrs): + if not getattr(settings, "LOGIN_NO_CAPTCHA_AUTH", False): + return {"code": 4000, "msg": "该接口暂未开通!", "data": None} + data = super().validate(attrs) + data["name"] = self.user.name + data["userId"] = self.user.id + return {"code": 2000, "msg": "请求成功", "data": data} + + +class LoginTokenView(TokenObtainPairView): + """ + 登录获取token接口 + """ + + serializer_class = LoginTokenSerializer + permission_classes = [] + + +class LogoutView(APIView): + def post(self, request): + return DetailResponse(msg="注销成功") + + +class ApiLoginSerializer(CustomModelSerializer): + """接口文档登录-序列化器""" + + username = serializers.CharField() + password = serializers.CharField() + + class Meta: + model = Users + fields = ["username", "password"] + + +class ApiLogin(APIView): + """接口文档的登录接口""" + + serializer_class = ApiLoginSerializer + authentication_classes = [] + permission_classes = [] + + def post(self, request): + username = request.data.get("username") + password = request.data.get("password") + user_obj = auth.authenticate( + request, + username=username, + password=hashlib.md5(password.encode(encoding="UTF-8")).hexdigest(), + ) + if user_obj: + login(request, user_obj) + return redirect("/") + else: + return ErrorResponse(msg="账号/密码错误") diff --git a/backend/dvadmin/system/views/login_log.py b/backend/dvadmin/system/views/login_log.py new file mode 100644 index 0000000..4d8bf74 --- /dev/null +++ b/backend/dvadmin/system/views/login_log.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/3 003 0:30 +@Remark: 按钮权限管理 +""" +from dvadmin.system.models import LoginLog +from dvadmin.utils.field_permission import FieldPermissionMixin +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class LoginLogSerializer(CustomModelSerializer): + """ + 登录日志权限-序列化器 + """ + + class Meta: + model = LoginLog + fields = "__all__" + read_only_fields = ["id"] + + +class LoginLogViewSet(CustomModelViewSet, FieldPermissionMixin): + """ + 登录日志接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = LoginLog.objects.all() + serializer_class = LoginLogSerializer + # extra_filter_class = [] diff --git a/backend/dvadmin/system/views/menu.py b/backend/dvadmin/system/views/menu.py new file mode 100644 index 0000000..33ed979 --- /dev/null +++ b/backend/dvadmin/system/views/menu.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/1 001 22:38 +@Remark: 菜单模块 +""" +from rest_framework import serializers +from rest_framework.decorators import action + +from dvadmin.system.models import Menu, RoleMenuPermission +from dvadmin.system.views.menu_button import MenuButtonSerializer +from dvadmin.utils.json_response import SuccessResponse, ErrorResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class MenuSerializer(CustomModelSerializer): + """ + 菜单表的简单序列化器 + """ + menuPermission = serializers.SerializerMethodField(read_only=True) + hasChild = serializers.SerializerMethodField() + + def get_menuPermission(self, instance): + queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value') + # MenuButtonSerializer(instance.menuPermission.all(), many=True) + if queryset: + return queryset + else: + return None + + def get_hasChild(self, instance): + hasChild = Menu.objects.filter(parent=instance.id) + if hasChild: + return True + return False + + class Meta: + model = Menu + fields = "__all__" + read_only_fields = ["id"] + + +class MenuCreateSerializer(CustomModelSerializer): + """ + 菜单表的创建序列化器 + """ + name = serializers.CharField(required=False) + + def create(self, validated_data): + menu_obj = Menu.objects.filter(parent_id=validated_data.get('parent', None)).order_by('-sort').first() + last_sort = menu_obj.sort if menu_obj else 0 + validated_data['sort'] = last_sort + 1 + return super().create(validated_data) + + class Meta: + model = Menu + fields = "__all__" + read_only_fields = ["id"] + + +class WebRouterSerializer(CustomModelSerializer): + """ + 前端菜单路由的简单序列化器 + """ + path = serializers.CharField(source="web_path") + title = serializers.CharField(source="name") + + class Meta: + model = Menu + fields = ( + 'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link','link_url', 'is_catalog', 'web_path', 'component', + 'component_name', 'cache', 'visible','is_iframe','is_affix', 'status') + read_only_fields = ["id"] + + +class MenuViewSet(CustomModelViewSet): + """ + 菜单管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = Menu.objects.all() + serializer_class = MenuSerializer + create_serializer_class = MenuCreateSerializer + update_serializer_class = MenuCreateSerializer + search_fields = ['name', 'status'] + filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog'] + + def list(self, request): + """懒加载""" + request.query_params._mutable = True + params = request.query_params + parent = params.get('parent', None) + page = params.get('page', None) + limit = params.get('limit', None) + if page: + del params['page'] + if limit: + del params['limit'] + if params: + if parent: + queryset = self.queryset.filter(parent=parent) + else: + queryset = self.queryset.filter() + else: + queryset = self.queryset.filter(parent__isnull=True) + queryset = self.filter_queryset(queryset) + serializer = MenuSerializer(queryset, many=True, request=request) + data = serializer.data + return SuccessResponse(data=data) + + @action(methods=['GET'], detail=False, permission_classes=[]) + def web_router(self, request): + """用于前端获取当前角色的路由""" + user = request.user + if user.is_superuser: + queryset = self.queryset.filter(status=1).order_by("sort") + else: + role_list = user.role.values_list('id', flat=True) + menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True) + queryset = Menu.objects.filter(id__in=menu_list).order_by("sort") + serializer = WebRouterSerializer(queryset, many=True, request=request) + data = serializer.data + return SuccessResponse(data=data, total=len(data), msg="获取成功") + + @action(methods=['GET'], detail=False, permission_classes=[]) + def get_all_menu(self, request): + """用于菜单管理获取所有的菜单""" + user = request.user + queryset = self.queryset.all() + if not user.is_superuser: + role_list = user.role.values_list('id', flat=True) + menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id') + queryset = Menu.objects.filter(id__in=menu_list) + serializer = WebRouterSerializer(queryset, many=True, request=request) + data = serializer.data + return SuccessResponse(data=data, total=len(data), msg="获取成功") + + @action(methods=['POST'], detail=False, permission_classes=[]) + def move_up(self, request): + """菜单上移""" + menu_id = request.data.get('menu_id') + try: + menu = Menu.objects.get(id=menu_id) + except Menu.DoesNotExist: + return ErrorResponse(msg="菜单不存在") + previous_menu = Menu.objects.filter(sort__lt=menu.sort, parent=menu.parent).order_by('-sort').first() + if previous_menu: + previous_menu.sort, menu.sort = menu.sort, previous_menu.sort + previous_menu.save() + menu.save() + return SuccessResponse(data=[], msg="上移成功") + + @action(methods=['POST'], detail=False, permission_classes=[]) + def move_down(self, request): + """菜单下移""" + menu_id = request.data['menu_id'] + try: + menu = Menu.objects.get(id=menu_id) + except Menu.DoesNotExist: + return ErrorResponse(msg="菜单不存在") + next_menu = Menu.objects.filter(sort__gt=menu.sort, parent=menu.parent).order_by('sort').first() + if next_menu: + next_menu.sort, menu.sort = menu.sort, next_menu.sort + next_menu.save() + menu.save() + return SuccessResponse(data=[], msg="下移成功") diff --git a/backend/dvadmin/system/views/menu_button.py b/backend/dvadmin/system/views/menu_button.py new file mode 100644 index 0000000..c1ca7c9 --- /dev/null +++ b/backend/dvadmin/system/views/menu_button.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/3 003 0:30 +@Remark: 菜单按钮管理 +""" +from django.db.models import F +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import MenuButton, RoleMenuButtonPermission, Menu +from dvadmin.utils.json_response import DetailResponse, SuccessResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + + + +class MenuButtonSerializer(CustomModelSerializer): + """ + 菜单按钮-序列化器 + """ + + class Meta: + model = MenuButton + fields = ['id', 'name', 'value', 'api', 'method','menu'] + read_only_fields = ["id"] + + + + +class MenuButtonCreateUpdateSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + + class Meta: + model = MenuButton + fields = "__all__" + read_only_fields = ["id"] + + +class MenuButtonViewSet(CustomModelViewSet): + """ + 菜单按钮接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = MenuButton.objects.order_by('create_datetime') + serializer_class = MenuButtonSerializer + create_serializer_class = MenuButtonCreateUpdateSerializer + update_serializer_class = MenuButtonCreateUpdateSerializer + extra_filter_class = [] + + def list(self, request, *args, **kwargs): + """ + 重写list方法 + :param request: + :param args: + :param kwargs: + :return: + """ + queryset = self.filter_queryset(self.get_queryset()).order_by('name') + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(serializer.data,msg="获取成功") + + @action(methods=['get'],detail=False,permission_classes=[IsAuthenticated]) + def menu_button_all_permission(self,request): + """ + 获取所有的按钮权限 + :param request: + :return: + """ + is_superuser = request.user.is_superuser + if is_superuser: + queryset = MenuButton.objects.values_list('value',flat=True) + else: + role_id = request.user.role.values_list('id', flat=True) + queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct() + return DetailResponse(data=queryset) + + @action(methods=['post'], detail=False, permission_classes=[IsAuthenticated]) + def batch_create(self, request, *args, **kwargs): + """ + 批量创建菜单“增删改查查”权限 + 创建的数据来源于菜单,需要规范创建菜单参数 + value:菜单的component_name:method + api:菜单的web_path增加'/api'前缀,并根据method增加{id} + """ + menu_obj = Menu.objects.filter(id=request.data['menu']).first() + result_list = [ + {'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api/{menu_obj.component_name}/', 'method': 1}, + {'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 3}, + {'menu': menu_obj.id, 'name': '编辑', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 2}, + {'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api/{menu_obj.component_name}/', 'method': 0}, + {'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 0}, + {'menu': menu_obj.id, 'name': '复制', 'value': f'{menu_obj.component_name}:Copy', 'api': f'/api/{menu_obj.component_name}/', 'method': 1}, + {'menu': menu_obj.id, 'name': '导入', 'value': f'{menu_obj.component_name}:Import', 'api': f'/api/{menu_obj.component_name}/import_data/', 'method': 1}, + {'menu': menu_obj.id, 'name': '导出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api/{menu_obj.component_name}/export_data/', 'method': 1},] + serializer = self.get_serializer(data=result_list, many=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return SuccessResponse(serializer.data, msg="批量创建成功") \ No newline at end of file diff --git a/backend/dvadmin/system/views/menu_field.py b/backend/dvadmin/system/views/menu_field.py new file mode 100644 index 0000000..26f2a42 --- /dev/null +++ b/backend/dvadmin/system/views/menu_field.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +from django.apps import apps +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import Role, MenuField +from dvadmin.utils.models import get_custom_app_models +from dvadmin.utils.viewset import CustomModelViewSet +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse + + +class MenuFieldSerializer(CustomModelSerializer): + """ + 列权限序列化器 + """ + + class Meta: + model = MenuField + fields = '__all__' + read_only_fields = ['id'] + + +class MenuFieldViewSet(CustomModelViewSet): + """ + 列权限视图集 + """ + queryset = MenuField.objects.order_by('-model') + serializer_class = MenuFieldSerializer + + def list(self, request, *args, **kwargs): + menu = request.query_params.get('menu') + if not menu: + return SuccessResponse([]) + queryset = self.filter_queryset(self.get_queryset().filter(menu=menu)) + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") + + def create(self, request, *args, **kwargs): + payload = request.data + for model in apps.get_models(): + if payload.get('model') == model.__name__: + break + else: + return ErrorResponse(msg='模型表不存在') + + if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists(): + return ErrorResponse(msg='‘%s’ 字段权限已有,不可重复创建' % payload.get('title')) + + return super().create(request, *args, **kwargs) + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def get_models(self, request): + """获取所有项目app下的model""" + res = [] + for model in get_custom_app_models(): + res.append({ + 'app': model['app'], + 'title': model['verbose'], + 'key': model['model'] + }) + return DetailResponse(res) + + @action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated]) + def auto_match_fields(self, request): + """自动匹配已有的字段""" + menu_id = request.data.get('menu') + model_name = request.data.get('model') + if not menu_id or not model_name: + return ErrorResponse( msg='参数错误') + for model in get_custom_app_models(): + if model['model'] != model_name: + continue + for field in model['fields']: + if MenuField.objects.filter( + menu_id=menu_id, model=model_name, field_name=field['name'] + ).exists(): + continue + data = { + 'menu': menu_id, + 'model': model_name, + 'field_name': field['name'], + 'title': str(field['title']), + } + serializer = self.get_serializer(data=data, request=request) + serializer.is_valid(raise_exception=True) + serializer.save() + return SuccessResponse(msg='匹配成功') diff --git a/backend/dvadmin/system/views/message_center.py b/backend/dvadmin/system/views/message_center.py new file mode 100644 index 0000000..52c065f --- /dev/null +++ b/backend/dvadmin/system/views/message_center.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +import json + +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer +from django_restql.fields import DynamicSerializerMethodField +from rest_framework import serializers +from rest_framework.decorators import action, permission_classes +from rest_framework.permissions import IsAuthenticated, AllowAny + +from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser +from dvadmin.utils.json_response import SuccessResponse, DetailResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class MessageCenterSerializer(CustomModelSerializer): + """ + 消息中心-序列化器 + """ + role_info = DynamicSerializerMethodField() + user_info = DynamicSerializerMethodField() + dept_info = DynamicSerializerMethodField() + is_read = serializers.BooleanField(read_only=True, source='target_user__is_read') + + def get_role_info(self, instance, parsed_query): + roles = instance.target_role.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.role import RoleSerializer + serializer = RoleSerializer( + roles, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + def get_user_info(self, instance, parsed_query): + if instance.target_type in (1, 2, 3): + return [] + users = instance.target_user.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.user import UserSerializer + serializer = UserSerializer( + users, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + def get_dept_info(self, instance, parsed_query): + dept = instance.target_dept.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.dept import DeptSerializer + serializer = DeptSerializer( + dept, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + class Meta: + model = MessageCenter + fields = "__all__" + read_only_fields = ["id"] + + +class MessageCenterTargetUserSerializer(CustomModelSerializer): + """ + 目标用户序列化器-序列化器 + """ + + class Meta: + model = MessageCenterTargetUser + fields = "__all__" + read_only_fields = ["id"] + + +class MessageCenterTargetUserListSerializer(CustomModelSerializer): + """ + 目标用户序列化器-序列化器 + """ + role_info = DynamicSerializerMethodField() + user_info = DynamicSerializerMethodField() + dept_info = DynamicSerializerMethodField() + is_read = serializers.SerializerMethodField() + + def get_is_read(self, instance): + user_id = self.request.user.id + message_center_id = instance.id + queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id, users_id=user_id).first() + if queryset: + return queryset.is_read + return False + + def get_role_info(self, instance, parsed_query): + roles = instance.target_role.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.role import RoleSerializer + serializer = RoleSerializer( + roles, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + def get_user_info(self, instance, parsed_query): + if instance.target_type in (1, 2, 3): + return [] + users = instance.target_user.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.user import UserSerializer + serializer = UserSerializer( + users, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + def get_dept_info(self, instance, parsed_query): + dept = instance.target_dept.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + from dvadmin.system.views.dept import DeptSerializer + serializer = DeptSerializer( + dept, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + class Meta: + model = MessageCenter + fields = "__all__" + read_only_fields = ["id"] + +def websocket_push(user_id, message): + """ + 主动推送消息 + """ + username = "user_" + str(user_id) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + username, + { + "type": "push.message", + "json": message + } + ) + +class MessageCenterCreateSerializer(CustomModelSerializer): + """ + 消息中心-新增-序列化器 + """ + + def save(self, **kwargs): + data = super().save(**kwargs) + initial_data = self.initial_data + target_type = initial_data.get('target_type') + # 在保存之前,根据目标类型,把目标用户查询出来并保存 + users = initial_data.get('target_user', []) + if target_type in [1]: # 按角色 + target_role = initial_data.get('target_role', []) + users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True) + if target_type in [2]: # 按部门 + target_dept = initial_data.get('target_dept', []) + users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True) + if target_type in [3]: # 系统通知 + users = Users.objects.values_list('id', flat=True) + targetuser_data = [] + for user in users: + targetuser_data.append({ + "messagecenter": data.id, + "users": user + }) + targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=self.request) + targetuser_instance.is_valid(raise_exception=True) + targetuser_instance.save() + for user in users: + unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count() + websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM', + "content": '您有一条新消息~', "unread": unread_count}) + return data + + class Meta: + model = MessageCenter + fields = "__all__" + read_only_fields = ["id"] + + +class MessageCenterViewSet(CustomModelViewSet): + """ + 消息中心接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = MessageCenter.objects.order_by('create_datetime') + serializer_class = MessageCenterSerializer + create_serializer_class = MessageCenterCreateSerializer + extra_filter_backends = [] + + def get_queryset(self): + if self.action == 'list': + return MessageCenter.objects.filter(creator=self.request.user.id).all() + return MessageCenter.objects.all() + + def retrieve(self, request, *args, **kwargs): + """ + 重写查看 + """ + pk = kwargs.get('pk') + user_id = self.request.user.id + queryset = MessageCenterTargetUser.objects.filter(users__id=user_id, messagecenter__id=pk).first() + if queryset: + queryset.is_read = True + queryset.save() + instance = self.get_object() + serializer = self.get_serializer(instance) + # 主动推送消息 + unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count() + websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT', + "content": '您查看了一条消息~', "unread": unread_count}) + return DetailResponse(data=serializer.data, msg="获取成功") + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def get_self_receive(self, request): + """ + 获取接收到的消息 + """ + self_user_id = self.request.user.id + # queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime') + queryset = MessageCenter.objects.filter(target_user__id=self_user_id) + # queryset = self.filter_queryset(queryset) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = MessageCenterTargetUserListSerializer(page, many=True, request=request) + return self.get_paginated_response(serializer.data) + serializer = MessageCenterTargetUserListSerializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def get_newest_msg(self, request): + """ + 获取最新的一条消息 + """ + self_user_id = self.request.user.id + queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last() + data = None + if queryset: + serializer = MessageCenterTargetUserListSerializer(queryset.messagecenter, many=False, request=request) + data = serializer.data + return DetailResponse(data=data, msg="获取成功") diff --git a/backend/dvadmin/system/views/operation_log.py b/backend/dvadmin/system/views/operation_log.py new file mode 100644 index 0000000..0c4ccbf --- /dev/null +++ b/backend/dvadmin/system/views/operation_log.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +""" +@author: 李强 +@contact: QQ:1206709430 +@Created on: 2021/6/8 003 0:30 +@Remark: 操作日志管理 +""" + +from dvadmin.system.models import OperationLog +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class OperationLogSerializer(CustomModelSerializer): + """ + 日志-序列化器 + """ + + class Meta: + model = OperationLog + fields = "__all__" + read_only_fields = ["id"] + + +class OperationLogCreateUpdateSerializer(CustomModelSerializer): + """ + 操作日志 创建/更新时的列化器 + """ + + class Meta: + model = OperationLog + fields = '__all__' + + +class OperationLogViewSet(CustomModelViewSet): + """ + 操作日志接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = OperationLog.objects.order_by('-create_datetime') + serializer_class = OperationLogSerializer + # permission_classes = [] diff --git a/backend/dvadmin/system/views/role.py b/backend/dvadmin/system/views/role.py new file mode 100644 index 0000000..f396860 --- /dev/null +++ b/backend/dvadmin/system/views/role.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/3 003 0:30 +@Remark: 角色管理 +""" +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import Role, Menu, MenuButton, Dept, Users +from dvadmin.system.views.dept import DeptSerializer +from dvadmin.system.views.menu import MenuSerializer +from dvadmin.system.views.menu_button import MenuButtonSerializer +from dvadmin.utils.crud_mixin import FastCrudMixin +from dvadmin.utils.field_permission import FieldPermissionMixin +from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.validator import CustomUniqueValidator +from dvadmin.utils.viewset import CustomModelViewSet +from dvadmin.utils.permission import CustomPermission + + +class RoleSerializer(CustomModelSerializer): + """ + 角色-序列化器 + """ + users = serializers.SerializerMethodField() + + @staticmethod + def get_users(instance): + users = instance.users_set.exclude(id=1).values('id', 'name', 'dept__name') + return users + + class Meta: + model = Role + fields = "__all__" + read_only_fields = ["id"] + + +class RoleCreateUpdateSerializer(CustomModelSerializer): + """ + 角色管理 创建/更新时的列化器 + """ + menu = MenuSerializer(many=True, read_only=True) + dept = DeptSerializer(many=True, read_only=True) + permission = MenuButtonSerializer(many=True, read_only=True) + key = serializers.CharField(max_length=50, + validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")]) + name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())]) + + def validate(self, attrs: dict): + return super().validate(attrs) + + # def save(self, **kwargs): + # is_superuser = self.request.user.is_superuser + # if not is_superuser: + # self.validated_data.pop('admin') + # data = super().save(**kwargs) + # return data + + class Meta: + model = Role + fields = '__all__' + + +class MenuPermissionSerializer(CustomModelSerializer): + """ + 菜单的按钮权限 + """ + menuPermission = serializers.SerializerMethodField() + + def get_menuPermission(self, instance): + is_superuser = self.request.user.is_superuser + if is_superuser: + queryset = MenuButton.objects.filter(menu__id=instance.id) + else: + menu_permission_id_list = self.request.user.role.values_list('permission', flat=True) + queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id) + serializer = MenuButtonSerializer(queryset, many=True, read_only=True) + return serializer.data + + class Meta: + model = Menu + fields = ['id', 'parent', 'name', 'menuPermission'] + + +class MenuButtonPermissionSerializer(CustomModelSerializer): + """ + 菜单和按钮权限 + """ + isCheck = serializers.SerializerMethodField() + + def get_isCheck(self, instance): + is_superuser = self.request.user.is_superuser + if is_superuser: + return True + else: + return MenuButton.objects.filter( + menu__id=instance.id, + role__id__in=self.request.user.role.values_list('id', flat=True), + ).exists() + + class Meta: + model = Menu + fields = '__all__' + + +class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin): + """ + 角色管理接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = Role.objects.all() + serializer_class = RoleSerializer + create_serializer_class = RoleCreateUpdateSerializer + update_serializer_class = RoleCreateUpdateSerializer + search_fields = ['name', 'key'] + + @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) + def set_role_users(self, request, pk): + """ + 设置 角色-用户 + :param request: + :return: + """ + data = request.data + direction = data.get('direction') + movedKeys = data.get('movedKeys') + role = Role.objects.get(pk=pk) + if direction == "left": + # left : 移除用户权限 + role.users_set.remove(*movedKeys) + else: + # right : 添加用户权限 + role.users_set.add(*movedKeys) + serializer = RoleSerializer(role) + return DetailResponse(data=serializer.data, msg="更新成功") + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, CustomPermission]) + def get_role_users(self, request): + """ + 获取角色已授权、未授权的用户 + 已授权的用户:1 + 未授权的用户:0 + """ + role_id = request.query_params.get('role_id', None) + + if not role_id: + return ErrorResponse(msg="请选择角色") + + if request.query_params.get('authorized', 0) == "1": + queryset = Users.objects.filter(role__id=role_id).exclude(is_superuser=True) + else: + queryset = Users.objects.exclude(role__id=role_id).exclude(is_superuser=True) + + if name := request.query_params.get('name', None): + queryset = queryset.filter(name__icontains=name) + + if dept := request.query_params.get('dept', None): + queryset = queryset.filter(dept=dept) + + page = self.paginate_queryset(queryset.values('id', 'name', 'dept__name')) + if page is not None: + return self.get_paginated_response(page) + + return SuccessResponse(data=page) + + @action(methods=['DELETE'], detail=True, permission_classes=[IsAuthenticated, CustomPermission]) + def remove_role_user(self, request, pk): + """ + 角色-删除用户 + """ + user_id = request.data.get('user_id', None) + + if not user_id: + return ErrorResponse(msg="请选择用户") + + role = self.get_object() + role.users_set.remove(*user_id) + + return SuccessResponse(msg="删除成功") + + @action(methods=['POST'], detail=True, permission_classes=[IsAuthenticated, CustomPermission]) + def add_role_users(self, request, pk): + """ + 角色-添加用户 + """ + users_id = request.data.get('users_id', None) + + if not users_id: + return ErrorResponse(msg="请选择用户") + + role = self.get_object() + role.users_set.add(*users_id) + + return DetailResponse(msg="添加成功") diff --git a/backend/dvadmin/system/views/role_menu.py b/backend/dvadmin/system/views/role_menu.py new file mode 100644 index 0000000..4a3cc95 --- /dev/null +++ b/backend/dvadmin/system/views/role_menu.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + + +from django.db.models import F +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import RoleMenuPermission, Menu, MenuButton +from dvadmin.utils.json_response import DetailResponse, ErrorResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class RoleMenuPermissionSerializer(CustomModelSerializer): + """ + 菜单按钮-序列化器 + """ + + class Meta: + model = RoleMenuPermission + fields = "__all__" + read_only_fields = ["id"] + + +class RoleMenuPermissionInitSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + + class Meta: + model = RoleMenuPermission + fields = "__all__" + read_only_fields = ["id"] + +class RoleMenuPermissionCreateUpdateSerializer(CustomModelSerializer): + """ + 初始化菜单按钮-序列化器 + """ + + class Meta: + model = RoleMenuPermission + fields = "__all__" + read_only_fields = ["id"] + + +class RoleMenuPermissionViewSet(CustomModelViewSet): + """ + 菜单按钮接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = RoleMenuPermission.objects.all() + serializer_class = RoleMenuPermissionSerializer + create_serializer_class = RoleMenuPermissionCreateUpdateSerializer + update_serializer_class = RoleMenuPermissionCreateUpdateSerializer + extra_filter_class = [] + + @action(methods=['post'],detail=False) + def save_auth(self,request): + """ + 保存页面菜单授权 + :param request: + :return: + """ + body = request.data + role_id = body.get('role',None) + if role_id is None: + return ErrorResponse(msg="未获取到角色参数") + menu_list = body.get('menu',None) + if menu_list is None: + return ErrorResponse(msg="未获取到菜单参数") + obj_list = RoleMenuPermission.objects.filter(role__id=role_id).values_list('menu__id',flat=True) + old_set = set(obj_list) + new_set = set(menu_list) + #need_update = old_set.intersection(new_set) # 需要更新的 + need_del = old_set.difference(new_set) # 需要删除的 + need_add = new_set.difference(old_set) # 需要新增的 + RoleMenuPermission.objects.filter(role__id=role_id,menu__in=list(need_del)).delete() + data = [{"role": role_id, "menu": item} for item in list(need_add)] + serializer = RoleMenuPermissionSerializer(data=data,many=True,request=request) + if serializer.is_valid(raise_exception=True): + serializer.save() + return DetailResponse(msg="保存成功",data=serializer.data) diff --git a/backend/dvadmin/system/views/role_menu_button_permission.py b/backend/dvadmin/system/views/role_menu_button_permission.py new file mode 100644 index 0000000..c041887 --- /dev/null +++ b/backend/dvadmin/system/views/role_menu_button_permission.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/3 003 0:30 +@Remark: 菜单按钮管理 +""" +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import RoleMenuButtonPermission, Menu, Dept, MenuButton, RoleMenuPermission, \ + MenuField, FieldPermission +from dvadmin.utils.json_response import DetailResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.viewset import CustomModelViewSet + + +class RoleMenuButtonPermissionSerializer(CustomModelSerializer): + """ + 角色-菜单-按钮-权限 查询序列化 + """ + + class Meta: + model = RoleMenuButtonPermission + fields = "__all__" + read_only_fields = ["id"] + + +class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer): + """ + 角色-菜单-按钮-权限 创建/修改序列化 + """ + menu_button__name = serializers.CharField(source='menu_button.name', read_only=True) + menu_button__value = serializers.CharField(source='menu_button.value', read_only=True) + + class Meta: + model = RoleMenuButtonPermission + fields = "__all__" + read_only_fields = ["id"] + + +class RoleMenuSerializer(CustomModelSerializer): + """ + 角色-菜单 序列化 + """ + isCheck = serializers.SerializerMethodField() + + def get_isCheck(self, instance): + params = self.request.query_params + data = self.request.data + return RoleMenuPermission.objects.filter( + menu_id=instance.id, + role_id=params.get('roleId', data.get('roleId')), + ).exists() + + class Meta: + model = Menu + fields = ["id", "name", "parent", "is_catalog", "isCheck"] + + +class RoleMenuButtonSerializer(CustomModelSerializer): + """ + 角色-菜单-按钮 序列化 + """ + isCheck = serializers.SerializerMethodField() + data_range = serializers.SerializerMethodField() + role_menu_btn_perm_id = serializers.SerializerMethodField() + dept = serializers.SerializerMethodField() + + def get_isCheck(self, instance): + params = self.request.query_params + data = self.request.data + return RoleMenuButtonPermission.objects.filter( + menu_button_id=instance.id, + role_id=params.get('roleId', data.get('roleId')), + ).exists() + + def get_data_range(self, instance): + obj = self.get_role_menu_btn_prem(instance) + if obj is None: + return None + return obj.data_range + + def get_role_menu_btn_perm_id(self, instance): + obj = self.get_role_menu_btn_prem(instance) + if obj is None: + return None + return obj.id + + def get_dept(self, instance): + obj = self.get_role_menu_btn_prem(instance) + if obj is None: + return None + return obj.dept.all().values_list('id', flat=True) + + def get_role_menu_btn_prem(self, instance): + params = self.request.query_params + data = self.request.data + obj = RoleMenuButtonPermission.objects.filter( + menu_button_id=instance.id, + role_id=params.get('roleId', data.get('roleId')), + ).first() + return obj + + class Meta: + model = MenuButton + fields = ['id', 'menu', 'name', 'isCheck', 'data_range', 'role_menu_btn_perm_id', 'dept'] + + +class RoleMenuFieldSerializer(CustomModelSerializer): + """ + 角色-菜单-字段 序列化 + """ + is_query = serializers.SerializerMethodField() + is_create = serializers.SerializerMethodField() + is_update = serializers.SerializerMethodField() + + def get_is_query(self, instance): + params = self.request.query_params + queryset = instance.menu_field.filter(role=params.get('roleId')).first() + if queryset: + return queryset.is_query + return False + + def get_is_create(self, instance): + params = self.request.query_params + queryset = instance.menu_field.filter(role=params.get('roleId')).first() + if queryset: + return queryset.is_create + return False + + def get_is_update(self, instance): + params = self.request.query_params + queryset = instance.menu_field.filter(role=params.get('roleId')).first() + if queryset: + return queryset.is_update + return False + + class Meta: + model = MenuField + fields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update'] + + +class RoleMenuButtonPermissionViewSet(CustomModelViewSet): + """ + 菜单按钮接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + queryset = RoleMenuButtonPermission.objects.all() + serializer_class = RoleMenuButtonPermissionSerializer + create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer + update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer + extra_filter_class = [] + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def get_role_menu(self, request): + """ + 获取 角色-菜单 + :param request: + :return: + """ + menu_queryset = Menu.objects.all() + serializer = RoleMenuSerializer(menu_queryset, many=True, request=request) + return DetailResponse(data=serializer.data) + + @action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated]) + def set_role_menu(self, request): + """ + 设置 角色-菜单 + :param request: + :return: + """ + data = request.data + roleId = data.get('roleId') + menuId = data.get('menuId') + isCheck = data.get('isCheck') + if isCheck: + # 添加权限:创建关联记录 + instance = RoleMenuPermission.objects.create(role_id=roleId, menu_id=menuId) + else: + # 删除权限:移除关联记录 + RoleMenuPermission.objects.filter(role_id=roleId, menu_id=menuId).delete() + menu_instance = Menu.objects.get(id=menuId) + serializer = RoleMenuSerializer(menu_instance, request=request) + return DetailResponse(data=serializer.data, msg="更新成功") + + @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) + def get_role_menu_btn_field(self, request): + """ + 获取 角色-菜单-按钮-列字段 + :param request: + :return: + """ + params = request.query_params + menuId = params.get('menuId', None) + menu_btn_queryset = MenuButton.objects.filter(menu_id=menuId) + menu_btn_serializer = RoleMenuButtonSerializer(menu_btn_queryset, many=True, request=request) + menu_field_queryset = MenuField.objects.filter(menu_id=menuId) + menu_field_serializer = RoleMenuFieldSerializer(menu_field_queryset, many=True, request=request) + return DetailResponse(data={'menu_btn': menu_btn_serializer.data, 'menu_field': menu_field_serializer.data}) + + @action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated]) + def set_role_menu_field(self, request, pk): + """ + 设置 角色-菜单-列字段 + """ + data = request.data + for col in data: + FieldPermission.objects.update_or_create( + role_id=pk, field_id=col.get('id'), + defaults={ + 'is_create': col.get('is_create'), + 'is_update': col.get('is_update'), + 'is_query': col.get('is_query'), + }) + + return DetailResponse(data=[], msg="更新成功") + + @action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated]) + def set_role_menu_btn(self, request): + """ + 设置 角色-菜单-按钮 + """ + data = request.data + isCheck = data.get('isCheck', None) + roleId = data.get('roleId', None) + btnId = data.get('btnId', None) + data_range = data.get('data_range', None) or 0 # 默认仅本人权限 + dept = data.get('dept', None) or [] # 默认空部门 + + if isCheck: + # 添加权限:创建关联记录 + instance = RoleMenuButtonPermission.objects.create(role_id=roleId, + menu_button_id=btnId, + data_range=data_range) + # 自定义部门权限 + if data_range == 4 and dept: + instance.dept.set(dept) + else: + # 删除权限:移除关联记录 + RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete() + menu_btn_instance = MenuButton.objects.get(id=btnId) + serializer = RoleMenuButtonSerializer(menu_btn_instance, request=request) + return DetailResponse(data=serializer.data, msg="更新成功") + + @action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated]) + def set_role_menu_btn_data_range(self, request): + """ + 设置 角色-菜单-按钮-权限 + """ + data = request.data + instance = RoleMenuButtonPermission.objects.get(id=data.get('role_menu_btn_perm_id')) + instance.data_range = data.get('data_range') + instance.dept.add(*data.get('dept')) + if not data.get('dept'): + instance.dept.clear() + instance.save() + serializer = RoleMenuButtonPermissionSerializer(instance, request=request) + return DetailResponse(data=serializer.data, msg="更新成功") + + @action(methods=['get'], detail=False, permission_classes=[IsAuthenticated]) + def role_to_dept_all(self, request): + """ + 当前用户角色下所能授权的部门:角色授权页面使用 + :param request: + :return: + """ + is_superuser = request.user.is_superuser + params = request.query_params + # 当前登录用户的角色 + role_list = request.user.role.values_list('id', flat=True) + + menu_button_id = params.get('menu_button') + # 当前登录用户角色可以分配的自定义部门权限 + dept_checked_disabled = RoleMenuButtonPermission.objects.filter( + role_id__in=role_list, menu_button_id=menu_button_id + ).values_list('dept', flat=True) + dept_list = Dept.objects.values('id', 'name', 'parent') + + data = [] + for dept in dept_list: + dept["disabled"] = False if is_superuser else dept["id"] not in dept_checked_disabled + data.append(dept) + return DetailResponse(data=data) diff --git a/backend/dvadmin/system/views/system_config.py b/backend/dvadmin/system/views/system_config.py new file mode 100644 index 0000000..8168b50 --- /dev/null +++ b/backend/dvadmin/system/views/system_config.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2022/1/21 003 0:30 +@Remark: 系统配置 +""" +import django_filters +from django.db.models import Q +from django_filters.rest_framework import BooleanFilter +from rest_framework import serializers +from rest_framework.views import APIView + +from application import dispatch +from dvadmin.system.models import SystemConfig +from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse +from dvadmin.utils.models import get_all_models_objects +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.validator import CustomValidationError +from dvadmin.utils.viewset import CustomModelViewSet + + +class SystemConfigCreateSerializer(CustomModelSerializer): + """ + 系统配置-新增时使用-序列化器 + """ + form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True) + + class Meta: + model = SystemConfig + fields = "__all__" + read_only_fields = ["id"] + + def validate_key(self, value): + """ + 验证key是否允许重复 + parent为空时不允许重复,反之允许 + """ + instance = SystemConfig.objects.filter(key=value, parent__isnull=True).exists() + if instance: + raise CustomValidationError('已存在相同变量名') + return value + + + + +class SystemConfigSerializer(CustomModelSerializer): + """ + 系统配置-序列化器 + """ + form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True) + + class Meta: + model = SystemConfig + fields = "__all__" + read_only_fields = ["id"] + + +class SystemConfigChinldernSerializer(CustomModelSerializer): + """ + 系统配置子级-序列化器 + """ + children = serializers.SerializerMethodField() + form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True) + + def get_children(self, instance): + queryset = SystemConfig.objects.filter(parent=instance) + serializer = SystemConfigSerializer(queryset, many=True) + return serializer.data + + class Meta: + model = SystemConfig + fields = "__all__" + read_only_fields = ["id"] + + +class SystemConfigListSerializer(CustomModelSerializer): + """ + 系统配置下模块的保存-序列化器 + """ + + def update(self, instance, validated_data): + instance_mapping = {obj.id: obj for obj in instance} + data_mapping = {item['id']: item for item in validated_data} + for obj_id, data in data_mapping.items(): + instance_obj = instance_mapping.get(obj_id, None) + if instance_obj is None: + return SystemConfig.objects.create(**data) + else: + return instance_obj.objects.update(**data) + + class Meta: + model = SystemConfig + fields = "__all__" + read_only_fields = ["id"] + + +class SystemConfigSaveSerializer(serializers.Serializer): + class Meta: + read_only_fields = ["id"] + list_serializer_class = SystemConfigListSerializer + + +class SystemConfigFilter(django_filters.rest_framework.FilterSet): + """ + 过滤器 + """ + parent__isnull = BooleanFilter(field_name='parent', lookup_expr="isnull") + + class Meta: + model = SystemConfig + fields = ['id', 'parent', 'status', 'parent__isnull'] + + +class SystemConfigViewSet(CustomModelViewSet): + """ + 系统配置接口 + """ + queryset = SystemConfig.objects.order_by('sort', 'create_datetime') + serializer_class = SystemConfigChinldernSerializer + create_serializer_class = SystemConfigCreateSerializer + retrieve_serializer_class = SystemConfigChinldernSerializer + # filter_fields = ['id','parent'] + filter_class = SystemConfigFilter + + def save_content(self, request): + body = request.data + data_mapping = {item['id']: item for item in body} + for obj_id, data in data_mapping.items(): + instance_obj = SystemConfig.objects.filter(id=obj_id).first() + if instance_obj is None: + # return SystemConfig.objects.create(**data) + serializer = SystemConfigCreateSerializer(data=data) + else: + serializer = SystemConfigCreateSerializer(instance_obj, data=data) + if serializer.is_valid(raise_exception=True): + serializer.save() + return DetailResponse(msg="保存成功") + + def get_association_table(self, request): + """ + 获取所有的model及字段信息 + """ + res = [ele.get('table') for ele in get_all_models_objects().values()] + return DetailResponse(msg="获取成功", data=res) + + def get_table_data(self, request, pk): + """ + 动态获取关联表的数据 + """ + instance = SystemConfig.objects.filter(id=pk).first() + if instance is None: + return ErrorResponse(msg="查询出错了~") + setting = instance.setting + if setting is None: + return ErrorResponse(msg="查询出错了~") + table = setting.get('table') # 获取model名 + model = get_all_models_objects(table).get("object", {}) + # 自己判断一下不存在 + queryset = model.objects.values() + body = request.query_params + search_value = body.get('search', None) + if search_value: + search_fields = setting.get('searchField') + filters = Q() + filters.connector = 'OR' + for item in search_fields: + filed = '{0}__icontains'.format(item.get('field')) + filters.children.append((filed, search_value)) + queryset = model.objects.filter(filters).values() + page = self.paginate_queryset(queryset) + if page is not None: + return self.get_paginated_response(queryset) + return SuccessResponse(msg="获取成功", data=queryset, total=len(queryset)) + + def get_relation_info(self, request): + """ + 查询关联的模板信息 + """ + body = request.query_params + var_name = body.get('varName', None) + table = body.get('table', None) + instance = SystemConfig.objects.filter(key=var_name, setting__table=table).first() + if instance is None: + return ErrorResponse(msg="未获取到关联信息") + relation_id = body.get('relationIds', None) + relationIds = [] + if relation_id is None: + return ErrorResponse(msg="未获取到关联信息") + if instance.form_item_type in [13]: + relationIds = [relation_id] + elif instance.form_item_type in [14]: + relationIds = relation_id.split(',') + queryset = SystemConfig.objects.filter(value__in=relationIds).first() + if queryset is None: + return ErrorResponse(msg="未获取到关联信息") + serializer = SystemConfigChinldernSerializer(queryset.parent) + return DetailResponse(msg="查询成功", data=serializer.data) + + +class InitSettingsViewSet(APIView): + """ + 获取初始化配置 + """ + authentication_classes = [] + permission_classes = [] + + def filter_system_config_values(self, data: dict): + """ + 过滤系统初始化配置 + :param data: + :return: + """ + if not self.request.query_params.get('key', ''): + return data + new_data = {} + for key in self.request.query_params.get('key', '').split('|'): + if key: + new_data.update(**dict(filter(lambda x: x[0].startswith(key), data.items()))) + return new_data + + def get(self, request): + data = dispatch.get_system_config() + if not data: + dispatch.refresh_system_config() + data = dispatch.get_system_config() + # 不返回后端专用配置 + backend_config = [f"{ele.get('parent__key')}.{ele.get('key')}" for ele in + SystemConfig.objects.filter(status=False, parent_id__isnull=False).values('parent__key', + 'key')] + data = dict(filter(lambda x: x[0] not in backend_config, data.items())) + data = self.filter_system_config_values(data=data) + return DetailResponse(data=data) diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py new file mode 100644 index 0000000..89b6533 --- /dev/null +++ b/backend/dvadmin/system/views/user.py @@ -0,0 +1,438 @@ +import hashlib + +from django.contrib.auth.hashers import make_password, check_password +from django_restql.fields import DynamicSerializerMethodField +from rest_framework import serializers +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from django.db import connection +from django.db.models import Q +from application import dispatch +from dvadmin.system.models import Users, Role, Dept +from dvadmin.system.views.role import RoleSerializer +from dvadmin.utils.json_response import ErrorResponse, DetailResponse, SuccessResponse +from dvadmin.utils.serializers import CustomModelSerializer +from dvadmin.utils.validator import CustomUniqueValidator +from dvadmin.utils.viewset import CustomModelViewSet + + +def recursion(instance, parent, result): + new_instance = getattr(instance, parent, None) + res = [] + data = getattr(instance, result, None) + if data: + res.append(data) + if new_instance: + array = recursion(new_instance, parent, result) + res += array + return res + + +class UserSerializer(CustomModelSerializer): + """ + 用户管理-序列化器 + """ + dept_name = serializers.CharField(source='dept.name', read_only=True) + role_info = DynamicSerializerMethodField() + dept_name_all = serializers.SerializerMethodField() + + class Meta: + model = Users + read_only_fields = ["id"] + exclude = ["password"] + extra_kwargs = { + "post": {"required": False}, + "mobile": {"required": False}, + } + + def get_dept_name_all(self, instance): + dept_name_all = recursion(instance.dept, "parent", "name") + dept_name_all.reverse() + return "/".join(dept_name_all) + + def get_role_info(self, instance, parsed_query): + roles = instance.role.all() + # You can do what ever you want in here + # `parsed_query` param is passed to BookSerializer to allow further querying + serializer = RoleSerializer( + roles, + many=True, + parsed_query=parsed_query + ) + return serializer.data + + +class UserCreateSerializer(CustomModelSerializer): + """ + 用户新增-序列化器 + """ + + username = serializers.CharField( + max_length=50, + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") + ], + ) + password = serializers.CharField( + required=False, + ) + + def validate_password(self, value): + """ + 对密码进行验证 + """ + md5 = hashlib.md5() + md5.update(value.encode('utf-8')) + md5_password = md5.hexdigest() + return make_password(md5_password) + + def save(self, **kwargs): + data = super().save(**kwargs) + data.dept_belong_id = data.dept_id + data.save() + if not self.validated_data.get('manage_dept', None): + data.manage_dept.add(data.dept_id) + data.post.set(self.initial_data.get("post", [])) + return data + + class Meta: + model = Users + fields = "__all__" + read_only_fields = ["id"] + extra_kwargs = { + "post": {"required": False}, + "mobile": {"required": False}, + } + + +class UserUpdateSerializer(CustomModelSerializer): + """ + 用户修改-序列化器 + """ + + username = serializers.CharField( + max_length=50, + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") + ], + ) + + def validate_is_active(self, value): + """ + 更改激活状态 + """ + if value: + self.initial_data["login_error_count"] = 0 + return value + + def save(self, **kwargs): + data = super().save(**kwargs) + data.dept_belong_id = data.dept_id + data.save() + if not self.validated_data.get('manage_dept', None): + data.manage_dept.add(data.dept_id) + data.post.set(self.initial_data.get("post", [])) + return data + + class Meta: + model = Users + read_only_fields = ["id", "password"] + fields = "__all__" + extra_kwargs = { + "post": {"required": False, "read_only": True}, + "mobile": {"required": False}, + } + + +class UserInfoUpdateSerializer(CustomModelSerializer): + """ + 用户修改-序列化器 + """ + mobile = serializers.CharField( + max_length=50, + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一") + ], + allow_blank=True + ) + + def update(self, instance, validated_data): + return super().update(instance, validated_data) + + class Meta: + model = Users + fields = ['email', 'mobile', 'avatar', 'name', 'gender'] + extra_kwargs = { + "post": {"required": False, "read_only": True}, + "mobile": {"required": False}, + } + + +class ExportUserProfileSerializer(CustomModelSerializer): + """ + 用户导出 序列化器 + """ + + last_login = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", required=False, read_only=True + ) + is_active = serializers.SerializerMethodField(read_only=True) + dept_name = serializers.CharField(source="dept.name", default="") + dept_owner = serializers.CharField(source="dept.owner", default="") + gender = serializers.CharField(source="get_gender_display", read_only=True) + + def get_is_active(self, instance): + return "启用" if instance.is_active else "停用" + + class Meta: + model = Users + fields = ( + "username", + "name", + "email", + "mobile", + "gender", + "is_active", + "last_login", + "dept_name", + "dept_owner", + ) + + +class UserProfileImportSerializer(CustomModelSerializer): + password = serializers.CharField(read_only=True, required=False) + + def save(self, **kwargs): + data = super().save(**kwargs) + password = hashlib.new( + "md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8") + ).hexdigest() + data.set_password(password) + data.save() + return data + + class Meta: + model = Users + exclude = ( + "post", + "user_permissions", + "groups", + "is_superuser", + "date_joined", + ) + + +class UserViewSet(CustomModelViewSet): + """ + 用户接口 + list:查询 + create:新增 + update:修改 + retrieve:单例 + destroy:删除 + """ + + queryset = Users.objects.exclude(is_superuser=1).all() + serializer_class = UserSerializer + create_serializer_class = UserCreateSerializer + update_serializer_class = UserUpdateSerializer + filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"] + search_fields = ["username", "name", "dept__name", "role__name"] + # 导出 + export_field_label = { + "username": "用户账号", + "name": "用户名称", + "email": "用户邮箱", + "mobile": "手机号码", + "gender": "用户性别", + "is_active": "帐号状态", + "last_login": "最后登录时间", + "dept_name": "部门名称", + "dept_owner": "部门负责人", + } + export_serializer_class = ExportUserProfileSerializer + # 导入 + import_serializer_class = UserProfileImportSerializer + import_field_dict = { + "username": "登录账号", + "name": "用户名称", + "email": "用户邮箱", + "mobile": "手机号码", + "gender": { + "title": "用户性别", + "choices": { + "data": {"未知": 2, "男": 1, "女": 0}, + } + }, + "is_active": { + "title": "帐号状态", + "choices": { + "data": {"启用": True, "禁用": False}, + } + }, + "dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}}, + "role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}}, + } + + @action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated]) + def user_info(self, request): + """获取当前用户信息""" + user = request.user + result = { + "id": user.id, + "username": user.username, + "name": user.name, + "mobile": user.mobile, + "user_type": user.user_type, + "gender": user.gender, + "email": user.email, + "avatar": user.avatar, + "dept": user.dept_id, + "is_superuser": user.is_superuser, + "role": user.role.values_list('id', flat=True), + "pwd_change_count":user.pwd_change_count + } + if hasattr(connection, 'tenant'): + result['tenant_id'] = connection.tenant and connection.tenant.id + result['tenant_name'] = connection.tenant and connection.tenant.name + dept = Dept.objects.filter(id=user.dept_id).values("id", "name").first() if user.dept_id else None + if dept: + result['dept_info'] = { + 'dept_id': dept["id"], + 'dept_name': dept["name"] + } + else: + result['dept_info'] = { + 'dept_id': None, + 'dept_name': "暂无部门" + } + role = getattr(user, 'role', None) + if role: + result['role_info'] = role.values('id', 'name', 'key') + return DetailResponse(data=result, msg="获取成功") + + @action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated]) + def update_user_info(self, request): + """修改当前用户信息""" + serializer = UserInfoUpdateSerializer(request.user, data=request.data, request=request) + serializer.is_valid(raise_exception=True) + serializer.save() + return DetailResponse(data=None, msg="修改成功") + + @action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated]) + def change_password(self, request, *args, **kwargs): + """密码修改""" + data = request.data + old_pwd = data.get("oldPassword") + new_pwd = data.get("newPassword") + new_pwd2 = data.get("newPassword2") + if old_pwd is None or new_pwd is None or new_pwd2 is None: + return ErrorResponse(msg="参数不能为空") + if new_pwd != new_pwd2: + return ErrorResponse(msg="两次密码不匹配") + verify_password = check_password(old_pwd, request.user.password) + if not verify_password: + old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest() + verify_password = check_password(str(old_pwd_md5), request.user.password) + # 创建用户时、自定义密码无法修改问题 + if not verify_password: + old_pwd_md5 = hashlib.md5(old_pwd_md5.encode(encoding='UTF-8')).hexdigest() + verify_password = check_password(str(old_pwd_md5), request.user.password) + if verify_password: + # request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest()) + request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest()) + request.user.pwd_change_count += 1 + request.user.save() + return DetailResponse(data=None, msg="修改成功") + else: + return ErrorResponse(msg="旧密码不正确") + + @action(methods=["post"], detail=False, permission_classes=[IsAuthenticated]) + def login_change_password(self, request, *args, **kwargs): + """初次登录进行密码修改""" + data = request.data + new_pwd = data.get("password") + new_pwd2 = data.get("password_regain") + if new_pwd != new_pwd2: + return ErrorResponse(msg="两次密码不匹配") + else: + request.user.password = make_password(new_pwd) + request.user.pwd_change_count += 1 + request.user.save() + return DetailResponse(data=None, msg="修改成功") + + @action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated]) + def reset_to_default_password(self, request,pk): + """恢复默认密码""" + if not self.request.user.is_superuser: + return ErrorResponse(msg="只允许超级管理员对其进行密码重置") + instance = Users.objects.filter(id=pk).first() + if instance: + default_password = dispatch.get_system_config_values("base.default_password") + md5_pwd = hashlib.md5(default_password.encode(encoding='UTF-8')).hexdigest() + instance.password = make_password(md5_pwd) + instance.save() + return DetailResponse(data=None, msg="密码重置成功") + else: + return ErrorResponse(msg="未获取到用户") + + @action(methods=["PUT"], detail=True) + def reset_password(self, request, pk): + """ + 密码重置 + """ + if not self.request.user.is_superuser: + return ErrorResponse(msg="只允许超级管理员对其进行密码重置") + instance = Users.objects.filter(id=pk).first() + data = request.data + new_pwd = data.get("newPassword") + new_pwd2 = data.get("newPassword2") + if instance: + if new_pwd != new_pwd2: + return ErrorResponse(msg="两次密码不匹配") + else: + instance.password = make_password(new_pwd) + instance.save() + return DetailResponse(data=None, msg="修改成功") + else: + return ErrorResponse(msg="未获取到用户") + + def list(self, request, *args, **kwargs): + dept_id = request.query_params.get('dept') + show_all = request.query_params.get('show_all') + if not dept_id: + dept_id = '' + if not show_all: + show_all = 0 + if int(show_all): + all_did = [dept_id] + def inner(did): + sub = Dept.objects.filter(parent_id=did) + if not sub.exists(): + return + for i in sub: + all_did.append(i.pk) + inner(i) + if dept_id != '': + inner(dept_id) + searchs = [ + Q(**{f+'__icontains':i}) + for f in self.search_fields + ] if (i:=request.query_params.get('search')) else [] + q_obj = [] + if searchs: + q = searchs[0] + for i in searchs[1:]: + q |= i + q_obj.append(Q(q)) + queryset = Users.objects.filter(*q_obj, dept_id__in=all_did) + else: + queryset = self.filter_queryset(self.get_queryset()) + else: + queryset = self.filter_queryset(self.get_queryset()) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, request=request) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") diff --git a/backend/dvadmin/utils/__init__.py b/backend/dvadmin/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/dvadmin/utils/backends.py b/backend/dvadmin/utils/backends.py new file mode 100644 index 0000000..4b28024 --- /dev/null +++ b/backend/dvadmin/utils/backends.py @@ -0,0 +1,39 @@ +import hashlib +import logging + +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.hashers import check_password +from django.utils import timezone + +from dvadmin.utils.validator import CustomValidationError + +logger = logging.getLogger(__name__) +UserModel = get_user_model() + + +class CustomBackend(ModelBackend): + """ + Django原生认证方式 + """ + + def authenticate(self, request, username=None, password=None, **kwargs): + msg = '%s 正在使用本地登录...' % username + logger.info(msg) + if username is None: + username = kwargs.get(UserModel.USERNAME_FIELD) + try: + user = UserModel._default_manager.get_by_natural_key(username) + except UserModel.DoesNotExist: + UserModel().set_password(password) + else: + 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) + if verify_password: + if self.user_can_authenticate(user): + user.last_login = timezone.now() + user.save() + return user + raise CustomValidationError("当前用户已被禁用,请联系管理员!") diff --git a/backend/dvadmin/utils/core_initialize.py b/backend/dvadmin/utils/core_initialize.py new file mode 100644 index 0000000..3fddf42 --- /dev/null +++ b/backend/dvadmin/utils/core_initialize.py @@ -0,0 +1,89 @@ +# 初始化基类 +import json +import os + +from django.apps import apps +from rest_framework import request + +from application import settings +from dvadmin.system.models import Users + + +class CoreInitialize: + """ + 使用方法:继承此类,重写 run方法,在 run 中调用 save 进行数据初始化 + """ + creator_id = None + reset = False + request = request + file_path = None + + def __init__(self, reset=False, creator_id=None, app=None): + """ + reset: 是否重置初始化数据 + creator_id: 创建人id + """ + self.reset = reset or self.reset + self.creator_id = creator_id or self.creator_id + self.app = app or '' + self.request.user = Users.objects.order_by('create_datetime').first() + + def init_base(self, Serializer, unique_fields=None): + model = Serializer.Meta.model + path_file = os.path.join(apps.get_app_config(self.app.split('.')[-1]).path, 'fixtures', + f'init_{Serializer.Meta.model._meta.model_name}.json') + if not os.path.isfile(path_file): + print("文件不存在,跳过初始化") + return + with open(path_file,encoding="utf-8") as f: + for data in json.load(f): + filter_data = {} + # 配置过滤条件,如果有唯一标识字段则使用唯一标识字段,否则使用全部字段 + if unique_fields: + for field in unique_fields: + if field in data: + filter_data[field] = data[field] + else: + for key, value in data.items(): + if isinstance(value, list) or value == None or value == '': + continue + filter_data[key] = value + instance = model.objects.filter(**filter_data).first() + data["reset"] = self.reset + serializer = Serializer(instance, data=data, request=self.request) + serializer.is_valid(raise_exception=True) + serializer.save() + print(f"[{self.app}][{model._meta.model_name}]初始化完成") + + def save(self, obj, data: list, name=None, no_reset=False): + name = name or obj._meta.verbose_name + print(f"正在初始化[{obj._meta.label} => {name}]") + if not no_reset and self.reset and obj not in settings.INITIALIZE_RESET_LIST: + try: + obj.objects.all().delete() + settings.INITIALIZE_RESET_LIST.append(obj) + except Exception: + pass + for ele in data: + m2m_dict = {} + new_data = {} + for key, value in ele.items(): + # 判断传的 value 为 list 的多对多进行抽离,使用set 进行更新 + if isinstance(value, list) and value and isinstance(value[0], int): + m2m_dict[key] = value + else: + new_data[key] = value + object, _ = obj.objects.get_or_create(id=ele.get("id"), defaults=new_data) + for key, m2m in m2m_dict.items(): + m2m = list(set(m2m)) + if m2m and len(m2m) > 0 and m2m[0]: + exec(f""" +if object.{key}: + values_list = object.{key}.all().values_list('id', flat=True) + values_list = list(set(list(values_list) + {m2m})) + object.{key}.set(values_list) +""") + print(f"初始化完成[{obj._meta.label} => {name}]") + + def run(self): + raise NotImplementedError('.run() must be overridden') diff --git a/backend/dvadmin/utils/crud_mixin.py b/backend/dvadmin/utils/crud_mixin.py new file mode 100644 index 0000000..9b4c018 --- /dev/null +++ b/backend/dvadmin/utils/crud_mixin.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +from rest_framework.decorators import action +from rest_framework.permissions import AllowAny + +from dvadmin.utils.json_response import DetailResponse + + +class FastCrudMixin: + """ + 定义快速CRUD数据操作的通用方法 + """ + # 需要CRUD的字段 + crud_fields = None + # 排除CRUD的字段 + exclude_fields = None + # 自定义CRUD的JSON + custom_crud_json = None + # 需要修改的CRUD键值对 + crud_update_key_value = None + + # 将Django的字段类型处理为JS类型 + def __handle_type(self, type): + if type in ['BigAutoField', 'CharField']: + return "input" + if type == 'DateTimeField': + return "datetime" + if type == 'DateField': + return "date" + if type == 'IntegerField': + return "number" + if type == 'BooleanField': + return "dict-switch" + + # 获取字段属性信息 + def __get_field_attribute(self): + result = [] + queryset = self.get_queryset() + __name = "" + __verbose_name = "" + __type = "text" + # 判断指定CRUD字段 + if self.crud_fields and type(self.crud_fields == list): + for item in self.crud_fields: + try: + field = queryset.model._meta.get_field(item) + field_type = field.get_internal_type() + __name = field.name + # 判断类型是否为外键类型,外键类型需要特殊方式获取verbose_name + if field_type in ['ForeignKey', 'OneToOneField', 'ManyToManyField']: + continue + # try: + # verbose_name = Users._meta.get_field(str(field.name)).verbose_name + # except: + # pass + else: + __verbose_name = field.verbose_name + __type = self.__handle_type(field_type) + except: + continue + result.append({"key": __name, "title": __verbose_name, "type": __type}) + else: + # 获取model的所有字段及属性 + model_fields = queryset.model._meta.get_fields() + # 遍历所有字段属性 + for field in model_fields: + field_type = field.get_internal_type() + __name = field.name + # 判断需要排除的CRUD字段 + if self.exclude_fields and type(self.exclude_fields == list): + if __name in self.exclude_fields: + continue + # 判断类型是否为外键类型,外键类型需要特殊方式获取verbose_name + if field_type in ['ForeignKey', 'OneToOneField', 'ManyToManyField']: + continue + # try: + # verbose_name = Users._meta.get_field(str(field.name)).verbose_name + # except: + # pass + else: + __verbose_name = field.verbose_name + __type = self.__handle_type(field_type) + result.append({"key": __name, "title": __verbose_name, "type": __type}) + return result + + #获取key + def __find_key(self,dct: dict, + target_key: str, + level: int = -1, + index: int = -1) -> tuple: + """Find a key within a nested dictionary and return its level and index.""" + for k, v in dct.items(): + level += 1 + index += 1 + if k == target_key: + return level, index + elif isinstance(v, list): + for i, dct_ in enumerate(v): + if isinstance(dct_, dict): + result = self.__find_key(dct_, target_key) + if result is not None: + return result + else: + continue + elif isinstance(v, str) or isinstance(v, int) or isinstance(v, float): + continue + + # 修改字典中key的value + def __update_nested_dict(self,nested_dict: dict, + target_key: str, + new_value) -> dict: + """Update a nested dictionary with a new value.""" + split_target_key = target_key.split('.') + if len(split_target_key) > 1: + new_dict = nested_dict[split_target_key[0]] + for item in split_target_key[1:-1]: + new_dict = new_dict[item] + self.__update_nested_dict(new_dict, split_target_key[-1], new_value) + else: + nested_dict[target_key] = new_value + return nested_dict + + # 处理crud,返回columns + def __handle_crud(self): + result = self.__get_field_attribute() + columns = dict() + for item in result: + key = item.get('key') + title = item.get('title') + type = item.get('type') + columns[key] = { + "title": title, + "key": key, + "type": type + } + # 对自定义的crud配置合并 + if self.custom_crud_json and isinstance(self.custom_crud_json,dict): + columns = columns | self.custom_crud_json + # 对curd进行修改配置 + if self.crud_update_key_value and isinstance(self.crud_update_key_value,dict): + for key, value in self.crud_update_key_value.items(): + columns = self.__update_nested_dict(columns,key,value) + return columns + @action(methods=['get'], detail=False,permission_classes=[AllowAny]) + def init_crud(self, request): + self.permission_classes = [AllowAny] + columns = self.__handle_crud() + expose = "({expose,dict})=>{" + ret = "return {" + res = "}}" + data = f"""{expose} + {ret} + columns:{columns} + {res} + """ + return DetailResponse(data=data) diff --git a/backend/dvadmin/utils/exception.py b/backend/dvadmin/utils/exception.py new file mode 100644 index 0000000..dfbf02c --- /dev/null +++ b/backend/dvadmin/utils/exception.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/2 002 16:06 +@Remark: 自定义异常处理 +""" +import logging +import traceback + +from django.db.models import ProtectedError +from django.http import Http404 +from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, NotAuthenticated +from rest_framework.status import HTTP_401_UNAUTHORIZED +from rest_framework.views import set_rollback, exception_handler + +from dvadmin.utils.json_response import ErrorResponse + +logger = logging.getLogger(__name__) + + +def _format_drf_detail(detail): + """ + 将 DRF ValidationError.detail 转为可读字符串。 + 避免嵌套列表(如 items 多行明细)在简单拼接时只剩「items:{}」等无效提示。 + """ + if detail is None: + return "" + if isinstance(detail, dict): + if not detail: + return "" + parts = [] + for k, v in detail.items(): + if isinstance(v, (list, tuple)): + subs = [] + for item in v: + if isinstance(item, dict): + inner = _format_drf_detail(item) + if inner: + subs.append(inner) + else: + subs.append(str(item)) + if subs: + parts.append("%s: %s" % (k, ";".join(subs))) + elif isinstance(v, dict): + inner = _format_drf_detail(v) + if inner: + parts.append("%s: %s" % (k, inner)) + else: + parts.append("%s: %s" % (k, v)) + return ";".join(parts) + if isinstance(detail, (list, tuple)): + subs = [] + for item in detail: + if isinstance(item, dict): + inner = _format_drf_detail(item) + if inner: + subs.append(inner) + else: + subs.append(str(item)) + return ";".join(subs) + return str(detail) + + +class CustomAuthenticationFailed(NotAuthenticated): + # 设置 status_code 属性为 400 + status_code = 400 + +def CustomExceptionHandler(ex, context): + """ + 统一异常拦截处理 + 目的:(1)取消所有的500异常响应,统一响应为标准错误返回 + (2)准确显示错误信息 + :param ex: + :param context: + :return: + """ + msg = '' + code = 4000 + # 调用默认的异常处理函数 + response = exception_handler(ex, context) + if isinstance(ex, AuthenticationFailed): + # 如果是身份验证错误 + if response and response.data.get('detail') == "Given token not valid for any token type": + code = 401 + msg = ex.detail + elif response and response.data.get('detail') == "Token is blacklisted": + # token在黑名单 + return ErrorResponse(status=HTTP_401_UNAUTHORIZED) + else: + code = 401 + msg = ex.detail + elif isinstance(ex,Http404): + code = 400 + msg = "接口地址不正确" + elif isinstance(ex, DRFAPIException): + set_rollback() + detail = ex.detail + if isinstance(detail, dict): + msg = _format_drf_detail(detail) or str(detail) + elif isinstance(detail, (list, tuple)): + msg = _format_drf_detail(detail) or str(detail) + else: + msg = str(detail) + elif isinstance(ex, ProtectedError): + set_rollback() + msg = "删除失败:该条数据与其他数据有相关绑定" + # elif isinstance(ex, DatabaseError): + # set_rollback() + # msg = "接口服务器异常,请联系管理员" + elif isinstance(ex, Exception): + logger.exception(traceback.format_exc()) + msg = str(ex) + return ErrorResponse(msg=msg, code=code) diff --git a/backend/dvadmin/utils/field_permission.py b/backend/dvadmin/utils/field_permission.py new file mode 100644 index 0000000..8702684 --- /dev/null +++ b/backend/dvadmin/utils/field_permission.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from django.db.models import F +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated + +from dvadmin.system.models import FieldPermission, MenuField +from dvadmin.utils.json_response import DetailResponse + + +def merge_permission(data): + """ + 合并权限 + """ + result = {} + for item in data: + field_name = item.pop('field_name') + if field_name not in result: + result[field_name] = item + else: + for key, value in item.items(): + result[field_name][key] = result[field_name][key] or value + return result + + +class FieldPermissionMixin: + @action(methods=['get'], detail=False, permission_classes=[IsAuthenticated]) + def field_permission(self, request): + """ + 获取字段权限 + """ + model = self.serializer_class.Meta.model.__name__ + user = request.user + # 创建一个默认字典来存储最终的结果 + if user.is_superuser == 1: + data = MenuField.objects.filter(model=model).values('field_name') + result = {item['field_name']: {"is_create": True, "is_query": True, "is_update": True} for item in data} + else: + roles = request.user.role.values_list('id', flat=True) + data = FieldPermission.objects.filter( + field__model=model, role__in=roles + ).values('is_create', 'is_query', 'is_update', field_name=F('field__field_name')) + result = merge_permission(data) + return DetailResponse(data=result) diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py new file mode 100644 index 0000000..e59b4f8 --- /dev/null +++ b/backend/dvadmin/utils/filters.py @@ -0,0 +1,512 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/6 006 12:39 +@Remark: 自定义过滤器 +""" +import operator +import re +from collections import OrderedDict +from functools import reduce + +import six +from django.db import models +from django.db.models import Q, F +from django.db.models.constants import LOOKUP_SEP +from django_filters.constants import ALL_FIELDS +from django_filters.rest_framework import DjangoFilterBackend +from django_filters.utils import get_model_field, translate_validation, deprecate +from rest_framework.request import Request +from rest_framework.filters import BaseFilterBackend +from django_filters.conf import settings + +from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton, Users +from util.currency import recursion_down_fast + + +class CoreModelFilterBankend(BaseFilterBackend): + """ + 自定义时间范围过滤器 + """ + def filter_queryset(self, request, queryset, view): + create_datetime_after = request.query_params.get('create_datetime_after', None) + create_datetime_before = request.query_params.get('create_datetime_before', None) + update_datetime_after = request.query_params.get('update_datetime_after', None) + update_datetime_before = request.query_params.get('update_datetime_before', None) + if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]): + create_filter = Q() + if create_datetime_after and create_datetime_before: + create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=f'{create_datetime_before} 23:59:59') + elif create_datetime_after: + create_filter &= Q(create_datetime__gte=create_datetime_after) + elif create_datetime_before: + create_filter &= Q(create_datetime__lte=f'{create_datetime_before} 23:59:59') + + # 更新时间范围过滤条件 + update_filter = Q() + if update_datetime_after and update_datetime_before: + update_filter &= Q(update_datetime__gte=update_datetime_after) & Q(update_datetime__lte=update_datetime_before) + elif update_datetime_after: + update_filter &= Q(update_datetime__gte=update_datetime_after) + elif update_datetime_before: + update_filter &= Q(update_datetime__lte=update_datetime_before) + # 结合两个时间范围过滤条件 + queryset = queryset.filter(create_filter & update_filter) + return queryset + return queryset + +def get_dept(dept_id: int, dept_all_list=None, dept_list=None): + """ + 递归获取部门的所有下级部门 + :param dept_id: 需要获取的部门id + :param dept_all_list: 所有部门列表 + :param dept_list: 递归部门list + :return: + """ + if not dept_all_list: + dept_all_list = Dept.objects.all().values("id", "parent") + if dept_list is None: + dept_list = [dept_id] + for ele in dept_all_list: + if ele.get("parent") == dept_id: + dept_list.append(ele.get("id")) + get_dept(ele.get("id"), dept_all_list, dept_list) + return list(set(dept_list)) + + +class DataLevelPermissionsFilter(BaseFilterBackend): + """ + 数据 级权限过滤器 + 0. 获取用户的部门id,没有部门则返回空 + 1. 判断过滤的数据是否有创建人所在部门 "creator" 字段,没有则返回全部 + 2. 如果用户没有关联角色则返回本部门数据 + 3. 根据角色的最大权限进行数据过滤(会有多个角色,进行去重取最大权限) + 3.1 判断用户是否为超级管理员角色/如果有1(所有数据) 则返回所有数据 + + 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) + 5. 自定数据权限 获取部门,根据部门过滤 + """ + + def filter_queryset(self, request, queryset, view): + """ + 接口白名单是否认证数据权限 + """ + api = request.path # 当前请求接口 + method = request.method # 当前请求方法 + methodList = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + method = methodList.index(method) + # ***接口白名单*** + api_white_list = ApiWhiteList.objects.filter(enable_datasource=False).values( + permission__api=F("url"), permission__method=F("method") + ) + api_white_list = [ + str(item.get("permission__api").replace("{id}", ".*?")) + + ":" + + str(item.get("permission__method")) + for item in api_white_list + if item.get("permission__api") + ] + for item in api_white_list: + new_api = f"{api}:{method}" + matchObj = re.match(item, new_api, re.M | re.I) + if matchObj is None: + continue + else: + return queryset + """ + 判断是否为超级管理员: + 如果不是超级管理员,则进入下一步权限判断 + """ + if request.user.is_superuser == 0: + return self._extracted_from_filter_queryset_33(request, queryset, api, method) + else: + return queryset + + # TODO Rename this here and in `filter_queryset` + def _extracted_from_filter_queryset_33(self, request, queryset, api, method): + # 0. 获取用户的部门id,没有部门则返回空 + user_dept_id = getattr(request.user, "dept_id", None) + if not user_dept_id: + return queryset.none() + + # 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段 + if not getattr(queryset.model, "dept_belong_id", None): + return queryset + + # 2. 如果用户没有关联角色则返回本部门数据 + if not hasattr(request.user, "role"): + return queryset.filter(dept_belong_id=user_dept_id) + + # 3. 根据所有角色 获取所有权限范围 + # (0, "仅本人数据权限"), + # (1, "本部门及以下数据权限"), + # (2, "本部门数据权限"), + # (3, "全部数据权限"), + # (4, "自定数据权限") + re_api = api + _pk = request.parser_context["kwargs"].get('pk') + if _pk: # 判断是否是单例查询 + re_api = re.sub(_pk,'{id}', api) + role_id_list = request.user.role.values_list('id', flat=True) + # 修复权限获取bug + menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True) + role_permission_list = [] + if menu_button_ids: + role_permission_list=RoleMenuButtonPermission.objects.filter( + role__in=role_id_list, + role__status=1, + menu_button_id__in=menu_button_ids).values( + 'data_range' + ) + dataScope_list = [] # 权限范围列表 + for ele in role_permission_list: + # 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据 + if ele.get("data_range") == 3: + return queryset + dataScope_list.append(ele.get("data_range")) + dataScope_list = list(set(dataScope_list)) + + # 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) + if 0 in dataScope_list: + return queryset.filter( + creator=request.user, dept_belong_id=user_dept_id + ) + + # 5. 自定数据权限 获取部门,根据部门过滤 + dept_list = [] + for ele in dataScope_list: + if ele == 1: + dept_list.append(user_dept_id) + dept_list.extend( + get_dept( + user_dept_id, + ) + ) + elif ele == 2: + dept_list.append(user_dept_id) + elif ele == 4: + dept_ids = RoleMenuButtonPermission.objects.filter( + role__in=role_id_list, + role__status=1, + data_range=4).values_list( + 'dept__id',flat=True + ) + dept_list.extend( + dept_ids + ) + if queryset.model._meta.model_name == 'dept': + return queryset.filter(id__in=list(set(dept_list))) + return queryset.filter(dept_belong_id__in=list(set(dept_list))) + + +class DataLevelPermissionsSubFilter(DataLevelPermissionsFilter): + """数据级权限过滤的子过滤器,过滤管理部门字段manage_dept""" + + def _extracted_from_filter_queryset_33(self, request:Request, queryset, api, method): + u:Users = request.user + if u.is_superuser: + return queryset + # (0, "仅本人数据权限"), + # (1, "本部门及以下数据权限"), + # (2, "本部门数据权限"), + # (3, "全部数据权限"), + # (4, "自定数据权限") + re_api = api + _pk = request.parser_context["kwargs"].get('pk') + if _pk: # 判断是否是单例查询 + re_api = re.sub(_pk,'{id}', api) + role_id_list = request.user.role.values_list('id', flat=True) + menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True) + role_permission_list = [] + if menu_button_ids: + role_permission_list=RoleMenuButtonPermission.objects.filter( + role__in=role_id_list, + role__status=1, + menu_button_id__in=menu_button_ids).values( + 'data_range' + ) + dataScope_list = [] # 权限范围列表 + for ele in role_permission_list: + # 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据 + if ele.get("data_range") == 3: + return queryset + dataScope_list.append(ele.get("data_range")) + dataScope_list = list(set(dataScope_list)) + # 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) + if 0 in dataScope_list: + return queryset.filter( + creator=request.user, dept_belong_id=u.dept_id + ) + dept_list = [] + # 5. 自定数据权限 获取部门,根据部门过滤 + for ele in dataScope_list: + if ele == 1: + dept_list.append(u.dept_id) + dept_list.extend( + get_dept( + u.dept_id, + ) + ) + elif ele == 2: + dept_list.append(u.dept_id) + elif ele == 4: + dept_ids = RoleMenuButtonPermission.objects.filter( + role__in=role_id_list, + role__status=1, + data_range=4).values_list( + 'dept__id',flat=True + ) + dept_list.extend( + dept_ids + ) + # 自己部门 交 管理部门 + if u.manage_dept.exists(): # 兼容旧数据 + for dept in u.manage_dept.all(): + dept_list.extend(recursion_down_fast(dept, 'parent', 'id')) + else: + dept_list = recursion_down_fast(u.dept, 'parent', 'id') + dept_list = set(recursion_down_fast(u.dept)) & set(dept_list) + # 自己创建的数据要能看到 + # 应对归属a管b、c等情况,如果自己创建数据则是a,不显式指定自己的数据就查不到 + if queryset.model._meta.model_name == 'dept': + return queryset.filter(Q(id__in=dept_list) | Q(creator=u)) + return queryset.filter(Q(dept_belong_id__in=dept_list) | Q(creator=u)) + + +class DataLevelPermissionMargeFilter(DataLevelPermissionsFilter): + def _extracted_from_filter_queryset_33(self, request, queryset, api, method): + queryset = super()._extracted_from_filter_queryset_33(request, queryset, api, method) + return DataLevelPermissionsSubFilter._extracted_from_filter_queryset_33(self, request, queryset, api, method) + + +class CustomDjangoFilterBackend(DjangoFilterBackend): + lookup_prefixes = { + "^": "istartswith", + "=": "iexact", + "@": "search", + "$": "iregex", + "~": "icontains", + } + filter_fields = "__all__" + + def construct_search(self, field_name, lookup_expr=None): + lookup = self.lookup_prefixes.get(field_name[0]) + if lookup: + field_name = field_name[1:] + else: + lookup = lookup_expr + if lookup: + if field_name.endswith(lookup): + return field_name + return LOOKUP_SEP.join([field_name, lookup]) + return field_name + + def find_filter_lookups(self, orm_lookups, search_term_key): + for lookup in orm_lookups: + # if lookup.find(search_term_key) >= 0: + new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup + # 修复条件搜索错误 bug + if new_lookup == search_term_key: + return lookup + return None + + def get_filterset_class(self, view, queryset=None): + """ + Return the `FilterSet` class used to filter the queryset. + """ + filterset_class = getattr(view, "filterset_class", None) + filterset_fields = getattr(view, "filterset_fields", None) + + # TODO: remove assertion in 2.1 + if filterset_class is None and hasattr(view, "filter_class"): + deprecate( + "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__ + ) + filterset_class = getattr(view, "filter_class", None) + + # TODO: remove assertion in 2.1 + if filterset_fields is None and hasattr(view, "filter_fields"): + deprecate( + "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__ + ) + self.filter_fields = getattr(view, "filter_fields", None) + if isinstance(self.filter_fields, (list, tuple)): + filterset_fields = [ + field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields + ] + else: + filterset_fields = self.filter_fields + + if filterset_class: + filterset_model = filterset_class._meta.model + + # FilterSets do not need to specify a Meta class + if filterset_model and queryset is not None: + assert issubclass( + queryset.model, filterset_model + ), "FilterSet model %s does not match queryset model %s" % ( + filterset_model, + queryset.model, + ) + + return filterset_class + + if filterset_fields and queryset is not None: + MetaBase = getattr(self.filterset_base, "Meta", object) + + class AutoFilterSet(self.filterset_base): + @classmethod + def get_all_model_fields(cls, model): + opts = model._meta + + return [ + f.name + for f in sorted(opts.fields + opts.many_to_many) + if (f.name == "id") + or not isinstance(f, models.AutoField) + and not (getattr(f.remote_field, "parent_link", False)) + ] + + @classmethod + def get_fields(cls): + """ + Resolve the 'fields' argument that should be used for generating filters on the + filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'. + """ + model = cls._meta.model + fields = cls._meta.fields + exclude = cls._meta.exclude + + assert not (fields is None and exclude is None), ( + "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " + "has been deprecated since 0.15.0 and is now disallowed. Add an explicit " + "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__ + ) + + # Setting exclude with no fields implies all other fields. + if exclude is not None and fields is None: + fields = ALL_FIELDS + + # Resolve ALL_FIELDS into all fields for the filterset's model. + if fields == ALL_FIELDS: + fields = cls.get_all_model_fields(model) + + # Remove excluded fields + exclude = exclude or [] + if not isinstance(fields, dict): + fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude] + else: + fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude] + + return OrderedDict(fields) + + @classmethod + def get_filters(cls): + """ + Get all filters for the filterset. This is the combination of declared and + generated filters. + """ + + # No model specified - skip filter generation + if not cls._meta.model: + return cls.declared_filters.copy() + + # Determine the filters that should be included on the filterset. + filters = OrderedDict() + fields = cls.get_fields() + undefined = [] + + for field_name, lookups in fields.items(): + field = get_model_field(cls._meta.model, field_name) + from django.db import models + from timezone_field import TimeZoneField + + # 不进行 过滤的model 类 + if isinstance(field, (models.JSONField, TimeZoneField, models.FileField)): + continue + # warn if the field doesn't exist. + if field is None: + undefined.append(field_name) + # 更新默认字符串搜索为模糊搜索 + if ( + isinstance(field, (models.CharField)) + and filterset_fields == "__all__" + and lookups == ["exact"] + ): + lookups = ["icontains"] + for lookup_expr in lookups: + filter_name = cls.get_filter_name(field_name, lookup_expr) + + # If the filter is explicitly declared on the class, skip generation + if filter_name in cls.declared_filters: + filters[filter_name] = cls.declared_filters[filter_name] + continue + + if field is not None: + filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr) + + # Allow Meta.fields to contain declared filters *only* when a list/tuple + if isinstance(cls._meta.fields, (list, tuple)): + undefined = [f for f in undefined if f not in cls.declared_filters] + + if undefined: + raise TypeError( + "'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined) + ) + + # Add in declared filters. This is necessary since we don't enforce adding + # declared filters to the 'Meta.fields' option + filters.update(cls.declared_filters) + return filters + + class Meta(MetaBase): + model = queryset.model + fields = filterset_fields + + return AutoFilterSet + + return None + + def filter_queryset(self, request, queryset, view): + filterset = self.get_filterset(request, queryset, view) + if filterset is None: + return queryset + if filterset.__class__.__name__ == "AutoFilterSet": + queryset = filterset.queryset + filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields + orm_lookup_dict = dict( + zip( + [field for field in filter_fields], + [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()], + ) + ) + orm_lookups = [ + self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items() + ] + # print(orm_lookups) + conditions = [] + queries = [] + for search_term_key in filterset.data.keys(): + orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) + if not orm_lookup or filterset.data.get(search_term_key) == '': + continue + filterset_data_len = len(filterset.data.getlist(search_term_key)) + if filterset_data_len == 1: + query = Q(**{orm_lookup: filterset.data[search_term_key]}) + queries.append(query) + elif filterset_data_len == 2: + orm_lookup += '__range' + query = Q(**{orm_lookup: filterset.data.getlist(search_term_key)}) + queries.append(query) + if len(queries) > 0: + conditions.append(reduce(operator.and_, queries)) + queryset = queryset.filter(reduce(operator.and_, conditions)) + return queryset + else: + return queryset + + if not filterset.is_valid() and self.raise_exception: + raise translate_validation(filterset.errors) + return filterset.qs diff --git a/backend/dvadmin/utils/git_utils.py b/backend/dvadmin/utils/git_utils.py new file mode 100644 index 0000000..cd7fde5 --- /dev/null +++ b/backend/dvadmin/utils/git_utils.py @@ -0,0 +1,104 @@ +import os +from git.repo import Repo +from git.repo.fun import is_git_dir + + +class GitRepository(object): + """ + git仓库管理 + """ + + def __init__(self, local_path, repo_url, branch='master'): + self.local_path = local_path + self.repo_url = repo_url + self.repo = None + self.initial(self.repo_url, branch) + + def initial(self, repo_url, branch): + """ + 初始化git仓库 + :param repo_url: + :param branch: + :return: + """ + if not os.path.exists(self.local_path): + os.makedirs(self.local_path) + git_local_path = os.path.join(self.local_path, '.git') + if not is_git_dir(git_local_path): + self.repo = Repo.clone_from(repo_url, to_path=self.local_path, branch=branch) + else: + self.repo = Repo(self.local_path) + + def pull(self): + """ + 从线上拉最新代码 + :return: + """ + self.repo.git.pull() + + def branches(self): + """ + 获取所有分支 + :return: + """ + branches = self.repo.remote().refs + return [item.remote_head for item in branches if item.remote_head not in ['HEAD', ]] + + def commits(self): + """ + 获取所有提交记录 + :return: + """ + commit_log = self.repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}', + max_count=50, + date='format:%Y-%m-%d %H:%M') + log_list = commit_log.split("\n") + return [eval(item) for item in log_list] + + def tags(self): + """ + 获取所有tag + :return: + """ + return [tag.name for tag in self.repo.tags] + + def tags_exists(self, tag): + """ + tag是否存在 + :return: + """ + return tag in self.tags() + + def change_to_branch(self, branch): + """ + 切换分支 + :param branch: + :return: + """ + self.repo.git.checkout(branch) + + def change_to_commit(self, branch, commit): + """ + 切换commit + :param branch: + :param commit: + :return: + """ + self.change_to_branch(branch=branch) + self.repo.git.reset('--hard', commit) + + def change_to_tag(self, tag): + """ + 切换tag + :param tag: + :return: + """ + self.repo.git.checkout(tag) + +# if __name__ == '__main__': +# local_path = os.path.join('codes', 't1') +# repo = GitRepository(local_path, remote_path) +# branch_list = repo.branches() +# print(branch_list) +# repo.change_to_branch('dev') +# repo.pull() diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py new file mode 100644 index 0000000..a3bccd9 --- /dev/null +++ b/backend/dvadmin/utils/import_export.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +import os +import re +from datetime import datetime + +import openpyxl +from django.conf import settings + +from dvadmin.utils.validator import CustomValidationError + + +def import_to_data(file_url, field_data, m2m_fields=None): + """ + 读取导入的excel文件 + :param file_url: + :param field_data: 首行数据源 + :param m2m_fields: 多对多字段 + :return: + """ + # 读取excel 文件 + file_path_dir = os.path.join(settings.BASE_DIR, file_url) + workbook = openpyxl.load_workbook(file_path_dir) + table = workbook[workbook.sheetnames[0]] + theader = tuple(table.values)[0] #Excel的表头 + is_update = '更新主键(勿改)' in theader #是否导入更新 + if is_update is False: #不是更新时,删除id列 + field_data.pop('id') + # 获取参数映射 + validation_data_dict = {} + for key, value in field_data.items(): + if isinstance(value, dict): + choices = value.get("choices", {}) + data_dict = {} + if choices.get("data"): + for k, v in choices.get("data").items(): + data_dict[k] = v + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values(choices.get("values_name"), "id") + for ele in data_list: + data_dict[ele.get(choices.get("values_name"))] = ele.get("id") + else: + continue + validation_data_dict[key] = data_dict + # 创建一个空列表,存储Excel的数据 + tables = [] + for i, row in enumerate(range(table.max_row)): + if i == 0: + continue + array = {} + for index, item in enumerate(field_data.items()): + items = list(item) + key = items[0] + values = items[1] + value_type = 'str' + if isinstance(values, dict): + value_type = values.get('type','str') + cell_value = table.cell(row=row + 1, column=index + 2).value + if cell_value is None or cell_value=='': + continue + elif value_type == 'date': + print(61, datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date()) + try: + cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date() + except: + raise CustomValidationError('日期格式不正确') + elif value_type == 'datetime': + cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S') + else: + # 由于excel导入数字类型后,会出现数字加 .0 的,进行处理 + if type(cell_value) is float and str(cell_value).split(".")[1] == "0": + cell_value = int(str(cell_value).split(".")[0]) + elif type(cell_value) is str: + cell_value = cell_value.strip(" \t\n\r") + if key in validation_data_dict: + array[key] = validation_data_dict.get(key, {}).get(cell_value, None) + if key in m2m_fields: + array[key] = list( + filter( + lambda x: x, + [ + validation_data_dict.get(key, {}).get(value, None) + for value in re.split(r"[,;:|.,;:\s]\s*", cell_value) + ], + ) + ) + else: + array[key] = cell_value + tables.append(array) + data = [i for i in tables if len(i) != 0] + return data diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py new file mode 100644 index 0000000..e188ff5 --- /dev/null +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +import datetime +from urllib.parse import quote + +from django.db import transaction +from django.http import HttpResponse +from openpyxl import Workbook +from openpyxl.worksheet.datavalidation import DataValidation +from openpyxl.utils import get_column_letter, quote_sheetname +from openpyxl.worksheet.table import Table, TableStyleInfo +from rest_framework.decorators import action +from rest_framework.request import Request + +from dvadmin.utils.import_export import import_to_data +from dvadmin.utils.json_response import DetailResponse, SuccessResponse +from dvadmin.utils.request_util import get_verbose_name +from dvadmin.system.tasks import async_export_data +from dvadmin.system.models import DownloadCenter + + +class ImportSerializerMixin: + """ + 自定义导入模板、导入功能 + """ + + # 导入字段 + import_field_dict = {} + # 导入序列化器 + import_serializer_class = None + # 表格表头最大宽度,默认50个字符 + export_column_width = 50 + + def is_number(self,num): + try: + float(num) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(num) + return True + except (TypeError, ValueError): + pass + return False + + def get_string_len(self, string): + """ + 获取字符串最大长度 + :param string: + :return: + """ + length = 4 + if string is None: + return length + if self.is_number(string): + return length + for char in string: + length += 2.1 if ord(char) > 256 else 1 + return round(length, 1) if length <= self.export_column_width else self.export_column_width + + @action(methods=['get','post'],detail=False) + @transaction.atomic # Django 事务,防止出错 + def import_data(self, request: Request, *args, **kwargs): + """ + 导入模板 + :param request: + :param args: + :param kwargs: + :return: + """ + assert self.import_field_dict, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__ + # 导出模板 + if request.method == "GET": + # 示例数据 + queryset = self.filter_queryset(self.get_queryset()) + # 导出excel 表 + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response[ + "Content-Disposition" + ] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}' + wb = Workbook() + ws1 = wb.create_sheet("data", 1) + ws1.sheet_state = "hidden" + ws = wb.active + row = get_column_letter(len(self.import_field_dict) + 1) + column = 10 + header_data = [ + "序号", + ] + validation_data_dict = {} + for index, ele in enumerate(self.import_field_dict.values()): + if isinstance(ele, dict): + header_data.append(ele.get("title")) + choices = ele.get("choices", {}) + if choices.get("data"): + data_list = [] + data_list.extend(choices.get("data").keys()) + validation_data_dict[ele.get("title")] = data_list + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True) + validation_data_dict[ele.get("title")] = list(data_list) + else: + continue + column_letter = get_column_letter(len(validation_data_dict)) + dv = DataValidation( + type="list", + formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[ele.get('title')]) + 1}", + allow_blank=True, + ) + ws.add_data_validation(dv) + dv.add(f"{get_column_letter(index + 2)}2:{get_column_letter(index + 2)}1048576") + else: + header_data.append(ele) + # 添加数据列 + ws1.append(list(validation_data_dict.keys())) + for index, validation_data in enumerate(validation_data_dict.values()): + for inx, ele in enumerate(validation_data): + ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele + # 插入导出模板正式数据 + df_len_max = [self.get_string_len(ele) for ele in header_data] + ws.append(header_data) + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table1", ref=f"A1:{row}{column}") # 名称管理器 + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) + tab.tableStyleInfo = style + ws.add_table(tab) + wb.save(response) + return response + else: + # 从excel中组织对应的数据结构,然后使用序列化器保存 + queryset = self.filter_queryset(self.get_queryset()) + # 获取多对多字段 + m2m_fields = [ + ele.name + for ele in queryset.model._meta.get_fields() + if hasattr(ele, "many_to_many") and ele.many_to_many == True + ] + import_field_dict = {'id':'更新主键(勿改)',**self.import_field_dict} + data = import_to_data(request.data.get("url"), import_field_dict, m2m_fields) + for ele in data: + filter_dic = {'id':ele.get('id')} + instance = filter_dic and queryset.filter(**filter_dic).first() + # print(156,ele) + serializer = self.import_serializer_class(instance, data=ele, request=request) + serializer.is_valid(raise_exception=True) + serializer.save() + return DetailResponse(msg=f"导入成功!") + + @action(methods=['get'],detail=False) + def update_template(self,request): + queryset = self.filter_queryset(self.get_queryset()) + assert self.import_field_dict, "'%s' 请配置对应的导入模板字段。" % self.__class__.__name__ + assert self.import_serializer_class, "'%s' 请配置对应的导入序列化器。" % self.__class__.__name__ + data = self.import_serializer_class(queryset, many=True, request=request).data + # 导出excel 表 + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' + wb = Workbook() + ws1 = wb.create_sheet("data", 1) + ws1.sheet_state = "hidden" + ws = wb.active + import_field_dict = {} + header_data = ["序号","更新主键(勿改)"] + hidden_header = ["#","id"] + #----设置选项---- + validation_data_dict = {} + for index, item in enumerate(self.import_field_dict.items()): + items = list(item) + key = items[0] + value = items[1] + if isinstance(value, dict): + header_data.append(value.get("title")) + hidden_header.append(value.get('display')) + choices = value.get("choices", {}) + if choices.get("data"): + data_list = [] + data_list.extend(choices.get("data").keys()) + validation_data_dict[value.get("title")] = data_list + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True) + validation_data_dict[value.get("title")] = list(data_list) + else: + continue + column_letter = get_column_letter(len(validation_data_dict)) + dv = DataValidation( + type="list", + formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[value.get('title')]) + 1}", + allow_blank=True, + ) + ws.add_data_validation(dv) + dv.add(f"{get_column_letter(index + 3)}2:{get_column_letter(index + 3)}1048576") + else: + header_data.append(value) + hidden_header.append(key) + # 添加数据列 + ws1.append(list(validation_data_dict.keys())) + for index, validation_data in enumerate(validation_data_dict.values()): + for inx, ele in enumerate(validation_data): + ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele + #-------- + df_len_max = [self.get_string_len(ele) for ele in header_data] + row = get_column_letter(len(hidden_header) + 1) + column = 1 + ws.append(header_data) + for index, results in enumerate(data): + results_list = [] + for h_index, h_item in enumerate(hidden_header): + for key, val in results.items(): + if key == h_item: + if val is None or val == "": + results_list.append("") + elif isinstance(val,list): + results_list.append(str(val)) + else: + results_list.append(val) + # 计算最大列宽度 + if isinstance(val,str): + result_column_width = self.get_string_len(val) + if h_index != 0 and result_column_width > df_len_max[h_index]: + df_len_max[h_index] = result_column_width + ws.append([index+1,*results_list]) + column += 1 + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) + tab.tableStyleInfo = style + ws.add_table(tab) + wb.save(response) + return response + + +class ExportSerializerMixin: + """ + 自定义导出功能 + """ + + # 导出字段 + export_field_label = [] + # 导出序列化器 + export_serializer_class = None + # 表格表头最大宽度,默认50个字符 + export_column_width = 50 + + def is_number(self,num): + try: + float(num) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(num) + return True + except (TypeError, ValueError): + pass + return False + + def get_string_len(self, string): + """ + 获取字符串最大长度 + :param string: + :return: + """ + length = 4 + if string is None: + return length + if self.is_number(string): + return length + for char in string: + length += 2.1 if ord(char) > 256 else 1 + return round(length, 1) if length <= self.export_column_width else self.export_column_width + + @action(methods=['get'],detail=False) + def export_data(self, request: Request, *args, **kwargs): + """ + 导出功能 + :param request: + :param args: + :param kwargs: + :return: + """ + queryset = self.filter_queryset(self.get_queryset()) + assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__ + assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__ + data = self.export_serializer_class(queryset, many=True, request=request).data + try: + async_export_data.delay( + data, + str(f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"), + DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务', dept_belong_id=request.user.dept_id).pk, + self.export_field_label + ) + return SuccessResponse(msg="导入任务已创建,请前往‘下载中心’等待下载") + except: + pass + # 导出excel 表 + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' + wb = Workbook() + ws = wb.active + header_data = ["序号", *self.export_field_label.values()] + hidden_header = ["#", *self.export_field_label.keys()] + df_len_max = [self.get_string_len(ele) for ele in header_data] + row = get_column_letter(len(self.export_field_label) + 1) + column = 1 + ws.append(header_data) + for index, results in enumerate(data): + results_list = [] + for h_index, h_item in enumerate(hidden_header): + for key,val in results.items(): + if key == h_item: + if val is None or val=="": + results_list.append("") + else: + results_list.append(val) + # 计算最大列宽度 + result_column_width = self.get_string_len(val) + if h_index !=0 and result_column_width > df_len_max[h_index]: + df_len_max[h_index] = result_column_width + ws.append([index + 1, *results_list]) + column += 1 + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) + tab.tableStyleInfo = style + ws.add_table(tab) + wb.save(response) + return response diff --git a/backend/dvadmin/utils/json_response.py b/backend/dvadmin/utils/json_response.py new file mode 100644 index 0000000..0d6c363 --- /dev/null +++ b/backend/dvadmin/utils/json_response.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/2 002 14:43 +@Remark: 自定义的JsonResonpse文件 +""" + +from rest_framework.response import Response + + +class SuccessResponse(Response): + """ + 标准响应成功的返回, SuccessResponse(data)或者SuccessResponse(data=data) + (1)默认code返回2000, 不支持指定其他返回码 + """ + + def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False, + content_type=None,page=1,limit=1,total=1): + std_data = { + "code": 2000, + "page": page, + "limit": limit, + "total": total, + "data": data, + "msg": msg + } + super().__init__(std_data, status, template_name, headers, exception, content_type) + + +class DetailResponse(Response): + """ + 不包含分页信息的接口返回,主要用于单条数据查询 + (1)默认code返回2000, 不支持指定其他返回码 + """ + + def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False, + content_type=None,): + std_data = { + "code": 2000, + "data": data, + "msg": msg + } + super().__init__(std_data, status, template_name, headers, exception, content_type) + + +class ErrorResponse(Response): + """ + 标准响应错误的返回,ErrorResponse(msg='xxx') + (1)默认错误码返回400, 也可以指定其他返回码:ErrorResponse(code=xxx) + """ + + def __init__(self, data=None, msg='error', code=400, status=None, template_name=None, headers=None, + exception=False, content_type=None): + std_data = { + "code": code, + "data": data, + "msg": msg + } + super().__init__(std_data, status, template_name, headers, exception, content_type) diff --git a/backend/dvadmin/utils/middleware.py b/backend/dvadmin/utils/middleware.py new file mode 100644 index 0000000..1185060 --- /dev/null +++ b/backend/dvadmin/utils/middleware.py @@ -0,0 +1,168 @@ +""" +日志 django中间件 +""" +import json +import logging + +from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import MultipleObjectsReturned +from django.http import HttpResponse, HttpResponseServerError +from django.utils.deprecation import MiddlewareMixin + +from dvadmin.system.models import OperationLog +from dvadmin.utils.request_util import get_request_user, get_request_ip, get_request_data, get_request_path, get_os, \ + get_browser, get_verbose_name + + +class ApiLoggingMiddleware(MiddlewareMixin): + """ + 用于记录API访问日志中间件 + """ + + def __init__(self, get_response=None): + super().__init__(get_response) + self.enable = getattr(settings, 'API_LOG_ENABLE', None) or False + self.methods = getattr(settings, 'API_LOG_METHODS', None) or set() + self.operation_log_id = None + + @classmethod + def __handle_request(cls, request): + request.request_ip = get_request_ip(request) + request.request_data = get_request_data(request) + request.request_path = get_request_path(request) + + def __handle_response(self, request, response): + + # 判断有无log_id属性,使用All记录时,会出现此情况 + if request.request_data.get('log_id', None) is None: + return + + # 移除log_id,不记录此ID + log_id = request.request_data.pop('log_id') + + # request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性 + body = getattr(request, 'request_data', {}) + # 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数) + if isinstance(body, dict) and body.get('password', ''): + body['password'] = '*' * len(body['password']) + if not hasattr(response, 'data') or not isinstance(response.data, dict): + response.data = {} + try: + if not response.data and response.content: + content = json.loads(response.content.decode()) + response.data = content if isinstance(content, dict) else {} + except Exception: + return + user = get_request_user(request) + info = { + 'request_ip': getattr(request, 'request_ip', 'unknown'), + 'creator': user if not isinstance(user, AnonymousUser) else None, + 'dept_belong_id': getattr(request.user, 'dept_id', None), + 'request_method': request.method, + 'request_path': request.request_path, + 'request_body': body, + 'response_code': response.data.get('code'), + 'request_os': get_os(request), + 'request_browser': get_browser(request), + 'request_msg': request.session.get('request_msg'), + 'status': True if response.data.get('code') in [2000, ] else False, + 'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')}, + } + # 记录日志不应影响业务接口返回;同时兼容历史脏数据导致的重复id问题 + try: + operation_log, _created = OperationLog.objects.update_or_create(defaults=info, id=log_id) + except MultipleObjectsReturned: + OperationLog.objects.filter(id=log_id).update(**info) + operation_log = ( + OperationLog.objects.filter(id=log_id) + .order_by('-create_datetime') + .first() + ) + except Exception: + return + + if operation_log and (not operation_log.request_modular) and settings.API_MODEL_MAP.get(request.request_path, None): + operation_log.request_modular = settings.API_MODEL_MAP[request.request_path] + operation_log.save(update_fields=['request_modular', 'update_datetime']) + + def process_view(self, request, view_func, view_args, view_kwargs): + if hasattr(view_func, 'cls') and hasattr(view_func.cls, 'queryset'): + if self.enable: + if self.methods == 'ALL' or request.method in self.methods: + log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset)) + log.save() + # self.operation_log_id = log.id + request.request_data['log_id'] = log.id + + return + + def process_request(self, request): + self.__handle_request(request) + + def process_response(self, request, response): + """ + 主要请求处理完之后记录 + :param request: + :param response: + :return: + """ + if self.enable: + if self.methods == 'ALL' or request.method in self.methods: + self.__handle_response(request, response) + return response + +logger = logging.getLogger("healthz") +class HealthCheckMiddleware(object): + """ + 存活检查中间件 + """ + def __init__(self, get_response): + self.get_response = get_response + # One-time configuration and initialization. + + def __call__(self, request): + if request.method == "GET": + if request.path == "/readiness": + return self.readiness(request) + elif request.path == "/healthz": + return self.healthz(request) + return self.get_response(request) + + def healthz(self, request): + """ + Returns that the server is alive. + """ + return HttpResponse("OK") + + def readiness(self, request): + # Connect to each database and do a generic standard SQL query + # that doesn't write any data and doesn't depend on any tables + # being present. + try: + from django.db import connections + for name in connections: + cursor = connections[name].cursor() + cursor.execute("SELECT 1;") + row = cursor.fetchone() + if row is None: + return HttpResponseServerError("db: invalid response") + except Exception as e: + logger.exception(e) + return HttpResponseServerError("db: cannot connect to database.") + + # Call get_stats() to connect to each memcached instance and get it's stats. + # This can effectively check if each is online. + try: + from django.core.cache import caches + from django.core.cache.backends.memcached import BaseMemcachedCache + for cache in caches.all(): + if isinstance(cache, BaseMemcachedCache): + stats = cache._cache.get_stats() + if len(stats) != len(cache._servers): + return HttpResponseServerError("cache: cannot connect to cache.") + except Exception as e: + logger.exception(e) + return HttpResponseServerError("cache: cannot connect to cache.") + + return HttpResponse("OK") diff --git a/backend/dvadmin/utils/models.py b/backend/dvadmin/utils/models.py new file mode 100644 index 0000000..1283351 --- /dev/null +++ b/backend/dvadmin/utils/models.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/5/31 031 22:08 +@Remark: 公共基础model类 +""" +from datetime import datetime +from importlib import import_module + +from application import settings +from django.apps import apps +from django.conf import settings +from django.db import models +from rest_framework.request import Request + +table_prefix = settings.TABLE_PREFIX # 数据库表名前缀 + + +class SoftDeleteQuerySet(models.QuerySet): + pass + + +class SoftDeleteManager(models.Manager): + """支持软删除""" + + def __init__(self, *args, **kwargs): + self.__add_is_del_filter = False + super(SoftDeleteManager, self).__init__(*args, **kwargs) + + def filter(self, *args, **kwargs): + # 考虑是否主动传入is_deleted + if not kwargs.get('is_deleted') is None: + self.__add_is_del_filter = True + return super(SoftDeleteManager, self).filter(*args, **kwargs) + + def get_queryset(self): + if self.__add_is_del_filter: + return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False) + return SoftDeleteQuerySet(self.model).exclude(is_deleted=True) + + def get_by_natural_key(self, name): + return SoftDeleteQuerySet(self.model).get(username=name) + + +class SoftDeleteModel(models.Model): + """ + 软删除模型 + 一旦继承,就将开启软删除 + """ + is_deleted = models.BooleanField(verbose_name="是否软删除", help_text='是否软删除', default=False, db_index=True) + objects = SoftDeleteManager() + + class Meta: + abstract = True + verbose_name = '软删除模型' + verbose_name_plural = verbose_name + + def delete(self, using=None, soft_delete=True, *args, **kwargs): + """ + 重写删除方法,直接开启软删除 + """ + if soft_delete: + self.is_deleted = True + self.save(using=using) + # 级联软删除关联对象 + for related_object in self._meta.related_objects: + related_model = getattr(self, related_object.get_accessor_name()) + # 处理一对多和多对多的关联对象 + if related_object.one_to_many or related_object.many_to_many: + related_objects = related_model.all() + elif related_object.one_to_one: + related_objects = [related_model] + else: + continue + + for obj in related_objects: + obj.delete(soft_delete=True) + else: + super().delete(using=using, *args, **kwargs) + + +class CoreModelManager(models.Manager): + def get_queryset(self): + is_deleted = getattr(self.model, 'is_soft_delete', False) + flow_work_status = getattr(self.model, 'flow_work_status', False) + queryset = super().get_queryset() + if flow_work_status: + queryset = queryset.filter(flow_work_status=1) + if is_deleted: + queryset = queryset.filter(is_deleted=False) + return queryset + def create(self,request: Request=None, **kwargs): + data = {**kwargs} + if request: + request_user = request.user + data["creator"] = request_user + data["modifier"] = request_user.id + data["dept_belong_id"] = request_user.dept_id + # 调用父类的create方法执行实际的创建操作 + return super().create(**data) + +class CoreModel(models.Model): + """ + 核心标准抽象模型模型,可直接继承使用 + 增加审计字段, 覆盖字段时, 字段名称请勿修改, 必须统一审计字段名称 + """ + id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id") + description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述") + creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, + verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, + db_constraint=False) + modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人") + dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, + verbose_name="数据归属部门") + update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", + verbose_name="修改时间") + create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间", + verbose_name="创建时间") + objects = CoreModelManager() + all_objects = models.Manager() + class Meta: + abstract = True + verbose_name = '核心模型' + verbose_name_plural = verbose_name + + def get_request_user(self, request: Request): + if getattr(request, "user", None): + return request.user + return None + + def get_request_user_id(self, request: Request): + if getattr(request, "user", None): + return getattr(request.user, "id", None) + return None + + def get_request_user_name(self, request: Request): + if getattr(request, "user", None): + return getattr(request.user, "name", None) + return None + + def get_request_user_username(self, request: Request): + if getattr(request, "user", None): + return getattr(request.user, "username", None) + return None + + def common_insert_data(self, request: Request): + data = { + 'create_datetime': datetime.now(), + 'creator': self.get_request_user(request) + } + return {**data, **self.common_update_data(request)} + + def common_update_data(self, request: Request): + return { + 'update_datetime': datetime.now(), + 'modifier': self.get_request_user_username(request) + } + + exclude_fields = [ + '_state', + 'pk', + 'id', + 'create_datetime', + 'update_datetime', + 'creator', + 'creator_id', + 'creator_pk', + 'creator_name', + 'modifier', + 'modifier_id', + 'modifier_pk', + 'modifier_name', + 'dept_belong_id', + ] + + def get_exclude_fields(self): + return self.exclude_fields + + def get_all_fields(self): + return self._meta.fields + + def get_all_fields_names(self): + return [field.name for field in self.get_all_fields()] + + def get_need_fields_names(self): + return [field.name for field in self.get_all_fields() if field.name not in self.exclude_fields] + + def to_data(self): + """将模型转化为字典(去除不包含字段)(注意与to_dict_data区分)。 + """ + res = {} + for field in self.get_need_fields_names(): + field_value = getattr(self, field) + res[field] = field_value.id if (issubclass(field_value.__class__, CoreModel)) else field_value + return res + + @property + def DATA(self): + return self.to_data() + + def to_dict_data(self): + """需要导出的字段(去除不包含字段)(注意与to_data区分) + """ + return {field: getattr(self, field) for field in self.get_need_fields_names()} + + @property + def DICT_DATA(self): + return self.to_dict_data() + + def insert(self, request): + """插入模型 + """ + assert self.pk is None, f'模型{self.__class__.__name__}还没有保存到数据中,不能手动指定ID' + validated_data = {**self.common_insert_data(request), **self.DICT_DATA} + return self.__class__._default_manager.create(**validated_data) + + def update(self, request, update_data: dict[str, any] = None): + """更新模型 + """ + assert isinstance(update_data, dict), 'update_data必须为字典' + validated_data = {**self.common_insert_data(request), **update_data} + for key, value in validated_data.items(): + # 不允许修改id,pk,uuid字段 + if key in ['id', 'pk', 'uuid']: + continue + if hasattr(self, key): + setattr(self, key, value) + self.save() + return self + + +def get_all_models_objects(model_name=None): + """ + 获取所有 models 对象 + :return: {} + """ + settings.ALL_MODELS_OBJECTS = {} + if not settings.ALL_MODELS_OBJECTS: + all_models = apps.get_models() + for item in list(all_models): + table = {"tableName": item._meta.verbose_name, "table": item.__name__, "tableFields": []} + for field in item._meta.fields: + fields = {"title": field.verbose_name, "field": field.name} + table['tableFields'].append(fields) + settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item}) + if model_name: + return settings.ALL_MODELS_OBJECTS[model_name] or {} + return settings.ALL_MODELS_OBJECTS or {} + + +def get_model_from_app(app_name): + """获取模型里的字段""" + model_module = import_module(app_name + '.models') + exclude_models = getattr(model_module, 'exclude_models', []) + filter_model = [ + value for key, value in model_module.__dict__.items() + if key != 'CoreModel' + and isinstance(value, type) + and issubclass(value, models.Model) + and key not in exclude_models + ] + model_list = [] + for model in filter_model: + if model.__name__ == 'AbstractUser': + continue + fields = [{'title': field.verbose_name, 'name': field.name, 'object': field} for field in model._meta.fields] + model_list.append({'app': app_name, 'verbose': model._meta.verbose_name, 'model': model.__name__, 'object': model, 'fields': fields}) + return model_list + + +def get_custom_app_models(app_name=None): + """ + 获取所有项目下的app里的models + """ + if app_name: + return get_model_from_app(app_name) + all_apps = apps.get_app_configs() + res = [] + for app in all_apps: + if app.name.startswith('django'): + continue + if app.name in settings.COLUMN_EXCLUDE_APPS: + continue + try: + all_models = get_model_from_app(app.name) + if all_models: + for model in all_models: + res.append(model) + except Exception as e: + pass + return res diff --git a/backend/dvadmin/utils/pagination.py b/backend/dvadmin/utils/pagination.py new file mode 100644 index 0000000..5e80e57 --- /dev/null +++ b/backend/dvadmin/utils/pagination.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 + +@contact: QQ:1638245306 + +@Created on: 2020/4/16 23:35 +""" +from collections import OrderedDict + +from django.core import paginator +from django.core.paginator import Paginator as DjangoPaginator, InvalidPage +from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response + + +class CustomPagination(PageNumberPagination): + page_size = 10 + page_size_query_param = "limit" + max_page_size = 999 + django_paginator_class = DjangoPaginator + + def paginate_queryset(self, queryset, request, view=None): + """ + Paginate a queryset if required, either returning a + page object, or `None` if pagination is not configured for this view. + """ + empty = True + + page_size = self.get_page_size(request) + if not page_size: + return None + + paginator = self.django_paginator_class(queryset, page_size) + page_number = request.query_params.get(self.page_query_param, 1) + if page_number in self.last_page_strings: + page_number = paginator.num_pages + + try: + self.page = paginator.page(page_number) + except InvalidPage as exc: + # msg = self.invalid_page_message.format( + # page_number=page_number, message=str(exc) + # ) + # raise NotFound(msg) + empty = False + + if paginator.num_pages > 1 and self.template is not None: + # The browsable API should display pagination controls. + self.display_page_controls = True + + self.request = request + + if not empty: + self.page = [] + + return list(self.page) + + def get_paginated_response(self, data): + code = 2000 + msg = 'success' + page = int(self.get_page_number(self.request, paginator)) or 1 + total = self.page.paginator.count if self.page else 0 + limit = int(self.get_page_size(self.request)) or 10 + is_next = self.page.has_next() if self.page else False + is_previous = self.page.has_previous() if self.page else False + + if not data: + code = 2000 + msg = "暂无数据" + data = [] + + return Response(OrderedDict([ + ('code', code), + ('msg', msg), + ('page', page), + ('limit', limit), + ('total', total), + ('is_next', is_next), + ('is_previous', is_previous), + ('data', data) + ])) diff --git a/backend/dvadmin/utils/permission.py b/backend/dvadmin/utils/permission.py new file mode 100644 index 0000000..688ab54 --- /dev/null +++ b/backend/dvadmin/utils/permission.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/6 006 10:30 +@Remark: 自定义权限 +""" +import re + +from django.contrib.auth.models import AnonymousUser +from django.db.models import F +from rest_framework.permissions import BasePermission + +from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission + + +def ValidationApi(reqApi, validApi): + """ + 验证当前用户是否有接口权限 + :param reqApi: 当前请求的接口 + :param validApi: 用于验证的接口 + :return: True或者False + """ + if validApi is not None: + valid_api = validApi.replace('{id}', '.*?') + matchObj = re.match(valid_api, reqApi, re.M | re.I) + if matchObj: + return True + else: + return False + else: + return False + + +class AnonymousUserPermission(BasePermission): + """ + 匿名用户权限 + """ + + def has_permission(self, request, view): + if isinstance(request.user, AnonymousUser): + return False + return True + + +class SuperuserPermission(BasePermission): + """ + 超级管理员权限类 + """ + + def has_permission(self, request, view): + if isinstance(request.user, AnonymousUser): + return False + # 判断是否是超级管理员 + if request.user.is_superuser: + return True + + +class AdminPermission(BasePermission): + """ + 普通管理员权限类 + """ + + def has_permission(self, request, view): + if isinstance(request.user, AnonymousUser): + return False + # 判断是否是超级管理员 + is_superuser = request.user.is_superuser + # 判断是否是管理员角色 + is_admin = request.user.role.values_list('admin', flat=True) + if is_superuser or True in is_admin: + return True + + +def ReUUID(api): + """ + 将接口的uuid替换掉 + :param api: + :return: + """ + pattern = re.compile(r'[a-f\d]{4}(?:[a-f\d]{4}-){4}[a-f\d]{12}/$') + m = pattern.search(api) + if m: + res = api.replace(m.group(0), ".*/") + return res + else: + return None + + +class CustomPermission(BasePermission): + """自定义权限""" + + def has_permission(self, request, view): + if isinstance(request.user, AnonymousUser): + return False + # 判断是否是超级管理员 + if request.user.is_superuser: + return True + else: + api = request.path # 当前请求接口 + method = request.method # 当前请求方法 + methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'] + method = methodList.index(method) + # ***接口白名单*** + api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method')) + api_white_list = [ + str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str( + item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')] + # ********# + if not hasattr(request.user, "role"): + return False + role_id_list = request.user.role.values_list('id', flat=True) + userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values( + permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口 + ApiList = [ + str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str( + item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')] + new_api_ist = api_white_list + ApiList + new_api = api + ":" + str(method) + for item in new_api_ist: + matchObj = re.match(item, new_api, re.M | re.I) + if matchObj is None: + continue + else: + return True + else: + return False diff --git a/backend/dvadmin/utils/request_util.py b/backend/dvadmin/utils/request_util.py new file mode 100644 index 0000000..3ef1771 --- /dev/null +++ b/backend/dvadmin/utils/request_util.py @@ -0,0 +1,223 @@ +""" +Request工具类 +""" +import json + +import requests +from django.conf import settings +from django.contrib.auth.models import AbstractBaseUser +from django.contrib.auth.models import AnonymousUser +from django.urls.resolvers import ResolverMatch +from rest_framework_simplejwt.authentication import JWTAuthentication +from user_agents import parse + +from dvadmin.system.models import LoginLog + + +def get_request_user(request): + """ + 获取请求user + (1)如果request里的user没有认证,那么则手动认证一次 + :param request: + :return: + """ + user: AbstractBaseUser = getattr(request, 'user', None) + if user and user.is_authenticated: + return user + try: + user, tokrn = JWTAuthentication().authenticate(request) + except Exception as e: + pass + return user or AnonymousUser() + + +def get_request_ip(request): + """ + 获取请求IP + :param request: + :return: + """ + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[-1].strip() + return ip + ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None) + return ip or 'unknown' + + +def get_request_data(request): + """ + 获取请求参数 + :param request: + :return: + """ + request_data = getattr(request, 'request_data', None) + if request_data: + return request_data + data: dict = {**request.GET.dict(), **request.POST.dict()} + if not data: + try: + body = request.body + if body: + data = json.loads(body) + except Exception as e: + pass + if not isinstance(data, dict): + data = {'data': data} + return data + + +def get_request_path(request, *args, **kwargs): + """ + 获取请求路径 + :param request: + :param args: + :param kwargs: + :return: + """ + request_path = getattr(request, 'request_path', None) + if request_path: + return request_path + values = [] + for arg in args: + if len(arg) == 0: + continue + if isinstance(arg, str): + values.append(arg) + elif isinstance(arg, (tuple, set, list)): + values.extend(arg) + elif isinstance(arg, dict): + values.extend(arg.values()) + if len(values) == 0: + return request.path + path: str = request.path + for value in values: + path = path.replace('/' + value, '/' + '{id}') + return path + + +def get_request_canonical_path(request, ): + """ + 获取请求路径 + :param request: + :param args: + :param kwargs: + :return: + """ + request_path = getattr(request, 'request_canonical_path', None) + if request_path: + return request_path + path: str = request.path + resolver_match: ResolverMatch = request.resolver_match + for value in resolver_match.args: + path = path.replace(f"/{value}", "/{id}") + for key, value in resolver_match.kwargs.items(): + if key == 'pk': + path = path.replace(f"/{value}", f"/{{id}}") + continue + path = path.replace(f"/{value}", f"/{{{key}}}") + + return path + + +def get_browser(request, ): + """ + 获取浏览器名 + :param request: + :param args: + :param kwargs: + :return: + """ + ua_string = request.META.get('HTTP_USER_AGENT', '') + if not ua_string: + return "" + user_agent = parse(ua_string) + return user_agent.get_browser() + + +def get_os(request, ): + """ + 获取操作系统 + :param request: + :param args: + :param kwargs: + :return: + """ + ua_string = request.META.get('HTTP_USER_AGENT', '') + if not ua_string: + return "" + user_agent = parse(ua_string) + return user_agent.get_os() + + +def get_verbose_name(queryset=None, view=None, model=None): + """ + 获取 verbose_name + :param request: + :param view: + :return: + """ + try: + if queryset is not None and hasattr(queryset, 'model'): + model = queryset.model + elif view and hasattr(view.get_queryset(), 'model'): + model = view.get_queryset().model + elif view and hasattr(view.get_serializer(), 'Meta') and hasattr(view.get_serializer().Meta, 'model'): + model = view.get_serializer().Meta.model + if model: + return getattr(model, '_meta').verbose_name + else: + model = queryset.model._meta.verbose_name + except Exception as e: + pass + return model if model else "" + + +def get_ip_analysis(ip): + """ + 获取ip详细概略 + :param ip: ip地址 + :return: + """ + data = { + "continent": "", + "country": "", + "province": "", + "city": "", + "district": "", + "isp": "", + "area_code": "", + "country_english": "", + "country_code": "", + "longitude": "", + "latitude": "" + } + if ip != 'unknown' and ip: + if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True): + try: + res = requests.get(url='https://ip.django-vue-admin.com/ip/analysis', params={"ip": ip}, timeout=5) + if res.status_code == 200: + res_data = res.json() + if res_data.get('code') == 0: + data = res_data.get('data') + return data + except Exception as e: + print(e) + return data + + +def save_login_log(request): + """ + 保存登录日志 + :return: + """ + ip = get_request_ip(request=request) + analysis_data = get_ip_analysis(ip) + analysis_data['username'] = request.user.username + analysis_data['ip'] = ip + analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT'])) + analysis_data['browser'] = get_browser(request) + analysis_data['os'] = get_os(request) + analysis_data['creator_id'] = request.user.id + analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '') + LoginLog.objects.create(**analysis_data) diff --git a/backend/dvadmin/utils/rustfs_storage.py b/backend/dvadmin/utils/rustfs_storage.py new file mode 100644 index 0000000..00cce3d --- /dev/null +++ b/backend/dvadmin/utils/rustfs_storage.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +""" +RustFS (S3-compatible) 文件存储工具 +""" +import uuid +from typing import Optional + +import boto3 +from botocore.config import Config +from botocore.exceptions import ClientError +from django.conf import settings + + +def get_rustfs_client(): + """ + 创建 RustFS S3 客户端 + """ + client = boto3.client( + 's3', + endpoint_url=f"{'https' if settings.RUSTFS_SECURE else 'http'}://{settings.RUSTFS_ENDPOINT}", + aws_access_key_id=settings.RUSTFS_ACCESS_KEY, + aws_secret_access_key=settings.RUSTFS_SECRET_KEY, + region_name=settings.RUSTFS_REGION or 'us-east-1', + config=Config(signature_version='s3v4'), + ) + return client + + +def rustfs_upload_file(file_obj, file_name: str) -> Optional[str]: + """ + 上传文件到 RustFS + + Args: + file_obj: 文件对象 (InMemoryUploadedFile 或 UploadedFile) + file_name: 原始文件名 + + Returns: + 文件访问URL,失败返回 None + """ + try: + # 生成唯一文件名 (uuid前缀避免冲突) + ext = file_name.split('.')[-1] if '.' in file_name else '' + unique_name = f"{uuid.uuid4().hex}.{ext}" if ext else uuid.uuid4().hex + + # 路径前缀处理 + path_prefix = getattr(settings, 'RUSTFS_PATH_PREFIX', '') + if path_prefix: + path_prefix = path_prefix.strip('/') + '/' + object_name = f"{path_prefix}{unique_name}" + + # 获取文件内容 + file_obj.seek(0) + file_content = file_obj.read() + + # 上传 + client = get_rustfs_client() + client.put_object( + Bucket=settings.RUSTFS_BUCKET, + Key=object_name, + Body=file_content, + ContentType=getattr(file_obj, 'content_type', 'application/octet-stream'), + ) + + # 生成访问URL + file_url = rustfs_get_file_url(object_name) + return file_url + + except ClientError as e: + print(f"RustFS upload error: {e}") + return None + except Exception as e: + print(f"RustFS upload unexpected error: {e}") + return None + + +def rustfs_delete_file(file_url: str) -> bool: + """ + 从 RustFS 删除文件 + + Args: + file_url: 文件完整URL或 object_key + + Returns: + 删除成功返回 True,否则 False + """ + try: + # 从URL中提取 object_key + object_key = file_url + if settings.RUSTFS_ENDPOINT in file_url: + # 提取 /bucket/key 部分 + parts = file_url.split(settings.RUSTFS_BUCKET + '/') + if len(parts) > 1: + object_key = parts[-1] + else: + return True # URL格式不对,当作已删除 + + client = get_rustfs_client() + client.delete_object(Bucket=settings.RUSTFS_BUCKET, Key=object_key) + return True + except ClientError as e: + print(f"RustFS delete error: {e}") + return False + + +def rustfs_get_file_url(object_key: str) -> str: + """ + 获取文件访问URL + + Args: + object_key: 对象存储的key + + Returns: + 完整的文件访问URL + """ + scheme = 'https' if settings.RUSTFS_SECURE else 'http' + endpoint = settings.RUSTFS_ENDPOINT + bucket = settings.RUSTFS_BUCKET + return f"{scheme}://{endpoint}/{bucket}/{object_key}" diff --git a/backend/dvadmin/utils/serializers.py b/backend/dvadmin/utils/serializers.py new file mode 100644 index 0000000..b0c411b --- /dev/null +++ b/backend/dvadmin/utils/serializers.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/1 001 22:47 +@Remark: 自定义序列化器 +""" +from rest_framework import serializers +from rest_framework.fields import empty +from rest_framework.request import Request +from rest_framework.serializers import ModelSerializer +from django.utils.functional import cached_property +from rest_framework.utils.serializer_helpers import BindingDict + +from dvadmin.system.models import Users +from django_restql.mixins import DynamicFieldsMixin + + +class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer): + """ + 增强DRF的ModelSerializer,可自动更新模型的审计字段记录 + (1)self.request能获取到rest_framework.request.Request对象 + """ + + # 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖 + modifier_field_id = "modifier" + modifier_name = serializers.SerializerMethodField(read_only=True) + + def get_modifier_name(self, instance): + if not hasattr(instance, "modifier"): + return None + queryset = ( + Users.objects.filter(id=instance.modifier) + .values_list("name", flat=True) + .first() + ) + if queryset: + return queryset + return None + + # 创建人的审计字段名称, 默认creator, 继承使用时可自定义覆盖 + creator_field_id = "creator" + creator_name = serializers.SlugRelatedField( + slug_field="name", source="creator", read_only=True + ) + # 数据所属部门字段 + dept_belong_id_field_name = "dept_belong_id" + # 添加默认时间返回格式 + create_datetime = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", required=False, read_only=True + ) + update_datetime = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", required=False, read_only=True + ) + + def __init__(self, instance=None, data=empty, request=None, **kwargs): + super().__init__(instance, data, **kwargs) + self.request: Request = request or self.context.get("request", None) + + def save(self, **kwargs): + return super().save(**kwargs) + + def create(self, validated_data): + if self.request: + if str(self.request.user) != "AnonymousUser": + if self.modifier_field_id in self.fields.fields: + validated_data[self.modifier_field_id] = self.get_request_user_id() + if self.creator_field_id in self.fields.fields: + validated_data[self.creator_field_id] = self.request.user + + if ( + self.dept_belong_id_field_name in self.fields.fields + and validated_data.get(self.dept_belong_id_field_name, None) is None + ): + validated_data[self.dept_belong_id_field_name] = getattr( + self.request.user, "dept_id", validated_data.get(self.dept_belong_id_field_name, None) + ) + return super().create(validated_data) + + def update(self, instance, validated_data): + if self.request: + if str(self.request.user) != "AnonymousUser": + if self.modifier_field_id in self.fields.fields: + validated_data[self.modifier_field_id] = self.get_request_user_id() + if hasattr(self.instance, self.modifier_field_id): + setattr( + self.instance, self.modifier_field_id, self.get_request_user_id() + ) + return super().update(instance, validated_data) + + def get_request_username(self): + if getattr(self.request, "user", None): + return getattr(self.request.user, "username", None) + return None + + def get_request_name(self): + if getattr(self.request, "user", None): + return getattr(self.request.user, "name", None) + return None + + def get_request_user_id(self): + if getattr(self.request, "user", None): + return getattr(self.request.user, "id", None) + return None + + @property + def errors(self): + # get errors + errors = super().errors + verbose_errors = {} + + # fields = { field.name: field.verbose_name } for each field in model + fields = {field.name: field.verbose_name for field in + self.Meta.model._meta.get_fields() if hasattr(field, 'verbose_name')} + + # iterate over errors and replace error key with verbose name if exists + for field_name, error in errors.items(): + if field_name in fields: + verbose_errors[str(fields[field_name])] = error + else: + verbose_errors[field_name] = error + return verbose_errors + + # @cached_property + # def fields(self): + # fields = BindingDict(self) + # for key, value in self.get_fields().items(): + # fields[key] = value + # + # if not hasattr(self, '_context'): + # return fields + # is_root = self.root == self + # parent_is_list_root = self.parent == self.root and getattr(self.parent, 'many', False) + # if not (is_root or parent_is_list_root): + # return fields + # + # try: + # request = self.request or self.context['request'] + # except KeyError: + # return fields + # params = getattr( + # request, 'query_params', getattr(request, 'GET', None) + # ) + # if params is None: + # pass + # try: + # filter_fields = params.get('_fields', None).split(',') + # except AttributeError: + # filter_fields = None + # + # try: + # omit_fields = params.get('_exclude', None).split(',') + # except AttributeError: + # omit_fields = [] + # + # existing = set(fields.keys()) + # if filter_fields is None: + # allowed = existing + # else: + # allowed = set(filter(None, filter_fields)) + # + # omitted = set(filter(None, omit_fields)) + # for field in existing: + # if field not in allowed: + # fields.pop(field, None) + # if field in omitted: + # fields.pop(field, None) + # + # return fields diff --git a/backend/dvadmin/utils/string_util.py b/backend/dvadmin/utils/string_util.py new file mode 100644 index 0000000..d28a948 --- /dev/null +++ b/backend/dvadmin/utils/string_util.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/8/21 021 9:48 +@Remark: +""" +import hashlib +import random + +CHAR_SET = ("2", "3", "4", "5", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", + "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z") + + +def random_str(number=16): + """ + 返回特定长度的随机字符串(非进制) + :return: + """ + result = "" + for i in range(0, number): + inx = random.randint(0, len(CHAR_SET) - 1) + result += CHAR_SET[inx] + return result + + +def has_md5(str, salt='123456'): + """ + md5 加密 + :param str: + :param salt: + :return: + """ + # satl是盐值,默认是123456 + str = str + salt + md = hashlib.md5() # 构造一个md5对象 + md.update(str.encode()) + res = md.hexdigest() + return res diff --git a/backend/dvadmin/utils/swagger.py b/backend/dvadmin/utils/swagger.py new file mode 100644 index 0000000..a1b32a2 --- /dev/null +++ b/backend/dvadmin/utils/swagger.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/8/12 012 10:25 +@Remark: swagger配置 +""" +from drf_yasg.generators import OpenAPISchemaGenerator +from drf_yasg.inspectors import SwaggerAutoSchema + +from application.settings import SWAGGER_SETTINGS + + +def get_summary(string): + if string is not None: + result = string.strip().replace(" ","").split("\n") + return result[0] + +class CustomSwaggerAutoSchema(SwaggerAutoSchema): + def get_tags(self, operation_keys=None): + tags = super().get_tags(operation_keys) + if "api" in tags and operation_keys: + # `operation_keys` 内容像这样 ['v1', 'prize_join_log', 'create'] + tags[0] = operation_keys[SWAGGER_SETTINGS.get('AUTO_SCHEMA_TYPE', 2)] + return tags + + def get_summary_and_description(self): + summary_and_description = super().get_summary_and_description() + summary = get_summary(self.__dict__.get('view').__doc__) + description = summary_and_description[1] + return summary,description + + +class CustomOpenAPISchemaGenerator(OpenAPISchemaGenerator): + def get_schema(self, request=None, public=False): + """Generate a :class:`.Swagger` object with custom tags""" + + swagger = super().get_schema(request, public) + swagger.tags = [ + { + "name": "token", + "description": "认证相关" + }, + ] + return swagger diff --git a/backend/dvadmin/utils/validator.py b/backend/dvadmin/utils/validator.py new file mode 100644 index 0000000..d214856 --- /dev/null +++ b/backend/dvadmin/utils/validator.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/2 002 17:03 +@Remark: 自定义验证器 +""" + +from django.db import DataError +from rest_framework.exceptions import APIException +from rest_framework.validators import UniqueValidator + + +class CustomValidationError(APIException): + """ + 继承并重写验证器返回的结果,避免暴露字段 + """ + + def __init__(self, detail): + self.detail = detail + + +def qs_exists(queryset): + try: + return queryset.exists() + except (TypeError, ValueError, DataError): + return False + + +def qs_filter(queryset, **kwargs): + try: + return queryset.filter(**kwargs) + except (TypeError, ValueError, DataError): + return queryset.none() + + +class CustomUniqueValidator(UniqueValidator): + """ + 继承,重写必填字段的验证器结果,防止字段暴露 + """ + + def filter_queryset(self, value, queryset, field_name): + """ + Filter the queryset to all instances matching the given attribute. + """ + filter_kwargs = {'%s__%s' % (field_name, self.lookup): value} + return qs_filter(queryset, **filter_kwargs) + + def exclude_current_instance(self, queryset, instance): + """ + If an instance is being updated, then do not include + that instance itself as a uniqueness conflict. + """ + if instance is not None: + return queryset.exclude(pk=instance.pk) + return queryset + + def __call__(self, value, serializer_field): + # Determine the underlying model field name. This may not be the + # same as the serializer field name if `source=<>` is set. + field_name = serializer_field.source_attrs[-1] + # Determine the existing instance, if this is an update operation. + instance = getattr(serializer_field.parent, 'instance', None) + + queryset = self.queryset + queryset = self.filter_queryset(value, queryset, field_name) + queryset = self.exclude_current_instance(queryset, instance) + if qs_exists(queryset): + raise CustomValidationError(self.message) + + def __repr__(self): + return super().__repr__() diff --git a/backend/dvadmin/utils/viewset.py b/backend/dvadmin/utils/viewset.py new file mode 100644 index 0000000..43160e6 --- /dev/null +++ b/backend/dvadmin/utils/viewset.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +""" +@author: 猿小天 +@contact: QQ:1638245306 +@Created on: 2021/6/1 001 22:57 +@Remark: 自定义视图集 +""" +import copy + +from django.db import transaction +from django_filters import DateTimeFromToRangeFilter +from django_filters.rest_framework import FilterSet +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.viewsets import ModelViewSet + +from dvadmin.utils.filters import CoreModelFilterBankend, DataLevelPermissionMargeFilter +from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin +from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse +from dvadmin.utils.permission import CustomPermission +from dvadmin.utils.models import get_custom_app_models, CoreModel +from dvadmin.system.models import FieldPermission, MenuField +from django_restql.mixins import QueryArgumentsMixin + + +class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMixin, QueryArgumentsMixin): + """ + 自定义的ModelViewSet: + 统一标准的返回格式;新增,查询,修改可使用不同序列化器 + (1)ORM性能优化, 尽可能使用values_queryset形式 + (2)xxx_serializer_class 某个方法下使用的序列化器(xxx=create|update|list|retrieve|destroy) + (3)filter_fields = '__all__' 默认支持全部model中的字段查询(除json字段外) + (4)import_field_dict={} 导入时的字段字典 {model值: model的label} + (5)export_field_label = [] 导出时的字段 + """ + values_queryset = None + ordering_fields = '__all__' + create_serializer_class = None + update_serializer_class = None + filter_fields = '__all__' + search_fields = () + extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionMargeFilter] + permission_classes = [CustomPermission] + import_field_dict = {} + export_field_label = {} + + def filter_queryset(self, queryset): + for backend in set(set(self.filter_backends) | set(self.extra_filter_class or [])): + queryset = backend().filter_queryset(self.request, queryset, self) + return queryset + + def get_queryset(self): + if getattr(self, 'values_queryset', None): + return self.values_queryset + return super().get_queryset() + + def get_serializer_class(self): + action_serializer_name = f"{self.action}_serializer_class" + action_serializer_class = getattr(self, action_serializer_name, None) + if action_serializer_class: + return action_serializer_class + return super().get_serializer_class() + + # 通过many=True直接改造原有的API,使其可以批量创建 + def get_serializer(self, *args, **kwargs): + serializer_class = self.get_serializer_class() + kwargs.setdefault('context', self.get_serializer_context()) + # 全部以可见字段为准 + can_see = self.get_menu_field(serializer_class) + # 排除掉序列化器级的字段(排除字段权限中未授权的字段) + # if not self.request.user.is_superuser: + # exclude_set = set(serializer_class._declared_fields.keys()) - set(can_see) + # for field in exclude_set: + # serializer_class._declared_fields.pop(field) + # meta = copy.deepcopy(serializer_class.Meta) + # meta.fields = list(can_see) + # serializer_class.Meta = meta + # 在分页器中使用 + self.request.permission_fields = can_see + if isinstance(self.request.data, list): + with transaction.atomic(): + return serializer_class(many=True, *args, **kwargs) + else: + return serializer_class(*args, **kwargs) + + def get_menu_field(self, serializer_class): + """获取字段权限""" + + if not any(model['object'] is serializer_class.Meta.model for model in get_custom_app_models()): + return [] + + # 匿名用户没有角色 + ret = FieldPermission.objects.filter(field__model=serializer_class.Meta.model.__name__) + if hasattr(self.request.user, 'role'): + roles = self.request.user.role.values_list('id', flat=True) + ret = ret.filter(is_query=True, role__in=roles) + + return ret.values_list('field__field_name', flat=True) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data, request=request) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + return DetailResponse(data=serializer.data, msg="新增成功") + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, request=request) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance) + return DetailResponse(data=serializer.data, msg="获取成功") + + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', False) + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data, request=request, partial=partial) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) + + if getattr(instance, '_prefetched_objects_cache', None): + # If 'prefetch_related' has been applied to a queryset, we need to + # forcibly invalidate the prefetch cache on the instance. + instance._prefetched_objects_cache = {} + return DetailResponse(data=serializer.data, msg="更新成功") + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + instance.delete() + return DetailResponse(data=[], msg="删除成功") + + keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING)) + @swagger_auto_schema(request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['keys'], + properties={'keys': keys} + ), operation_summary='批量删除') + @action(methods=['delete'], detail=False) + def multiple_delete(self, request, *args, **kwargs): + request_data = request.data + keys = request_data.get('keys', None) + if keys: + self.get_queryset().filter(id__in=keys).delete() + return SuccessResponse(data=[], msg="删除成功") + else: + return ErrorResponse(msg="未获取到keys字段") + + @action(methods=['post'], detail=False) + def get_by_ids(self, request): + """通过IDS列表获取数据""" + ids = request.data.get('ids', []) + if ids and ids != ['']: + queryset = self.get_queryset().filter(id__in=ids) + serializer = self.get_serializer(queryset, many=True) + return DetailResponse(data=serializer.data) + return DetailResponse(data=None) diff --git a/backend/gunicorn_conf.py b/backend/gunicorn_conf.py new file mode 100644 index 0000000..ca2fb32 --- /dev/null +++ b/backend/gunicorn_conf.py @@ -0,0 +1,48 @@ +# gunicorn.conf +# coding:utf-8 +# 启动命令:gunicorn -c gunicorn.py application.asgi:application +import multiprocessing +# 并行工作进程数, int,cpu数量*2+1 推荐进程数 +workers = multiprocessing.cpu_count() * 2 + 1 +# 指定每个进程开启的线程数 +threads = 3 +# 绑定的ip与端口 +bind = '0.0.0.0:8000' +# 设置守护进程,将进程交给第三方管理 +daemon = 'false' +# 工作模式协程,默认的是sync模式,推荐使用 gevent,此处使用与uvicorn配合使用 uvicorn.workers.UvicornWorker +worker_class = 'uvicorn.workers.UvicornWorker' +# 设置最大并发量(每个worker处理请求的工作线程数,正整数,默认为1) +worker_connections = 10000 +# 最大客户端并发数量,默认情况下这个值为1000。此设置将影响gevent和eventlet工作模式 +# 每个工作进程将在处理max_requests请求后自动重新启动该进程 +max_requests = 10000 +max_requests_jitter = 200 +# 设置进程文件目录 +pidfile = './gunicorn.pid' +# 日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置 +loglevel = 'info' +# 设置gunicorn访问日志格式,错误日志无法设置 +access_log_format = '' # worker_class 为 uvicorn.workers.UvicornWorker 时,日志格式为Django的loggers +# 监听队列 +backlog = 512 +#进程名 +proc_name = 'gunicorn_process' +# 设置超时时间120s,默认为30s。按自己的需求进行设置timeout = 120 +timeout = 120 +# 超时重启 +graceful_timeout = 300 +# 在keep-alive连接上等待请求的秒数,默认情况下值为2。一般设定在1~5秒之间。 +keepalive = 3 +# HTTP请求行的最大大小,此参数用于限制HTTP请求行的允许大小,默认情况下,这个值为4094。 +# 值是0~8190的数字。此参数可以防止任何DDOS攻击 +limit_request_line = 5120 +# 限制HTTP请求中请求头字段的数量。 +# 此字段用于限制请求头字段的数量以防止DDOS攻击,与limit-request-field-size一起使用可以提高安全性。 +# 默认情况下,这个值为100,这个值不能超过32768 +limit_request_fields = 101 +# 限制HTTP请求中请求头的大小,默认情况下这个值为8190。 +# 值是一个整数或者0,当该值为0时,表示将对请求头大小不做限制 +limit_request_field_size = 0 +# 记录到标准输出 +accesslog = '-' diff --git a/backend/logs/__init__.py b/backend/logs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..c0419d9 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,17 @@ +import multiprocessing +import os +import sys + +root_path = os.getcwd() +sys.path.append(root_path) +import uvicorn +from application.settings import LOGGING + +if __name__ == '__main__': + multiprocessing.freeze_support() + workers = 4 + if os.sys.platform.startswith('win'): + # Windows操作系统 + workers = None + uvicorn.run("application.asgi:application", reload=False, host="0.0.0.0", port=8000, workers=workers, + log_config=LOGGING) diff --git a/backend/manage.py b/backend/manage.py new file mode 100644 index 0000000..c3564fd --- /dev/null +++ b/backend/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/backend/plugins/__init__.py b/backend/plugins/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/backend/plugins/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/backend/plugins/eip_sync/sync/__init__.py b/backend/plugins/eip_sync/sync/__init__.py new file mode 100644 index 0000000..5cdd1d5 --- /dev/null +++ b/backend/plugins/eip_sync/sync/__init__.py @@ -0,0 +1 @@ +# EIP / external sync plugin package diff --git a/backend/plugins/eip_sync/sync/adapters/__init__.py b/backend/plugins/eip_sync/sync/adapters/__init__.py new file mode 100644 index 0000000..6ee423f --- /dev/null +++ b/backend/plugins/eip_sync/sync/adapters/__init__.py @@ -0,0 +1 @@ +# Adapters register themselves with SyncFactory on import. diff --git a/backend/plugins/eip_sync/sync/adapters/misc_material.py b/backend/plugins/eip_sync/sync/adapters/misc_material.py new file mode 100644 index 0000000..0210fa6 --- /dev/null +++ b/backend/plugins/eip_sync/sync/adapters/misc_material.py @@ -0,0 +1,133 @@ +from __future__ import annotations + +import json +import re +from typing import Any, Dict, Optional + +from apps.pisadmin.miscprocurement.models import MiscProcMaterial + +from sync.base import BaseSyncAdapter, SyncDirection +from sync.factory import SyncFactory + + +def _first_str(data: Dict[str, Any], *keys: str) -> str: + for k in keys: + v = data.get(k) + if v is None: + continue + s = str(v).strip() + if s: + return s + return "" + + +def _parse_category_id(raw: Any) -> Optional[int]: + if raw is None or raw == "": + return None + if isinstance(raw, int): + return raw + s = str(raw).strip() + if re.fullmatch(r"-?\d+", s): + return int(s) + return None + + +def _build_extra_description(data: Dict[str, Any], category_used_as_id: bool) -> str: + extra: Dict[str, Any] = {} + ac = data.get("accountCode") or data.get("account_code") + if ac is not None and str(ac).strip() != "": + extra["accountCode"] = str(ac).strip() + cat = data.get("Category") or data.get("category") + if cat is not None and str(cat).strip() != "" and not category_used_as_id: + extra["category"] = str(cat).strip() + for label, keys in ( + ("materialNameEn", ("materialNameEn", "material_name_en")), + ("materialNameVi", ("materialNameVi", "material_name_vi")), + ): + v = _first_str(data, *keys) + if v: + extra[label] = v + if not extra: + return "" + try: + blob = json.dumps(extra, ensure_ascii=False) + except (TypeError, ValueError): + return "" + if len(blob) <= 255: + return blob + return blob[:252] + "..." + + +class MiscMaterialSyncAdapter(BaseSyncAdapter): + adapter_name = "misc_material" + direction = SyncDirection.EIP_TO_PIS + + def validate(self, data: Dict[str, Any]) -> bool: + company = _first_str(data, "companyCode", "company_code") + code = _first_str(data, "materialCode", "material_code") + return bool(company and code) + + def transform_to_local(self, data: Dict[str, Any]) -> Dict[str, Any]: + company_code = _first_str(data, "companyCode", "company_code") + partid = _first_str(data, "materialCode", "material_code") + partid_name = _first_str( + data, + "materialName", + "materialNameZh", + "material_name", + "material_name_zh", + ) + specification = _first_str(data, "specification") + unit = _first_str(data, "unit") + + cat_raw = data.get("Category") if "Category" in data else data.get("category") + partid_category_id = _parse_category_id(cat_raw) + category_used_as_id = partid_category_id is not None + + description = _build_extra_description(data, category_used_as_id) + if not description: + desc_existing = _first_str(data, "description") + if desc_existing: + description = desc_existing[:255] + + external_id = f"{company_code}_{partid}" + return { + "company_code": company_code, + "partid": partid, + "partid_name": partid_name, + "specification": specification, + "unit": unit, + "partid_category_id": partid_category_id, + "description": description, + "external_id": external_id, + "status": 1, + } + + def save_to_local(self, transformed_data: Dict[str, Any]) -> bool: + external_id = transformed_data.pop("external_id", "") + _ = external_id + company_code = transformed_data.get("company_code") + partid = transformed_data.get("partid") + defaults = { + "partid_name": transformed_data.get("partid_name") or "", + "specification": transformed_data.get("specification") or "", + "unit": transformed_data.get("unit") or "", + "partid_category_id": transformed_data.get("partid_category_id"), + "status": transformed_data.get("status", 1), + } + desc = transformed_data.get("description") + if desc: + defaults["description"] = desc[:255] + + MiscProcMaterial.objects.update_or_create( + company_code=company_code, + partid=partid, + defaults=defaults, + ) + return True + + def transform_to_remote(self, data: Dict[str, Any]) -> Dict[str, Any]: + return {} + + +SyncFactory.register("misc_material", MiscMaterialSyncAdapter) diff --git a/backend/plugins/eip_sync/sync/apps.py b/backend/plugins/eip_sync/sync/apps.py new file mode 100644 index 0000000..302ad81 --- /dev/null +++ b/backend/plugins/eip_sync/sync/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + + +class SyncConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "sync" + verbose_name = "EIP 数据同步" + + def ready(self): + # Register adapters with SyncFactory + from sync.adapters import misc_material # noqa: F401 diff --git a/backend/plugins/eip_sync/sync/base.py b/backend/plugins/eip_sync/sync/base.py new file mode 100644 index 0000000..28ba837 --- /dev/null +++ b/backend/plugins/eip_sync/sync/base.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any, Dict, Optional + + +class SyncDirection(str, Enum): + EIP_TO_PIS = "eip_to_pis" + PIS_TO_EIP = "pis_to_eip" + + +class SyncStatus(str, Enum): + PENDING = "pending" + SUCCESS = "success" + FAILED = "failed" + RETRYING = "retrying" + + +@dataclass +class SyncOperationResult: + """In-memory result of a single sync operation (not the ORM SyncRecord).""" + + adapter_name: str = "" + direction: SyncDirection = SyncDirection.EIP_TO_PIS + external_id: str = "" + payload: Optional[Dict[str, Any]] = None + response: Optional[Dict[str, Any]] = None + status: SyncStatus = SyncStatus.PENDING + error_message: str = "" + retry_count: int = 0 + created_at: Optional[datetime] = None + + +class BaseSyncAdapter(ABC): + adapter_name: str = "" + direction: SyncDirection = SyncDirection.EIP_TO_PIS + + def __init__(self, eip_client=None, logger=None): + self.eip_client = eip_client + self.logger = logger + + @abstractmethod + def validate(self, data: Dict[str, Any]) -> bool: + pass + + @abstractmethod + def transform_to_local(self, data: Dict[str, Any]) -> Dict[str, Any]: + pass + + def transform_to_remote(self, data: Dict[str, Any]) -> Dict[str, Any]: + pass + + @abstractmethod + def save_to_local(self, transformed_data: Dict[str, Any]) -> bool: + pass + + def push_to_local(self, data: Dict[str, Any]) -> SyncOperationResult: + created_at = datetime.now() + record = SyncOperationResult( + adapter_name=self.adapter_name, + direction=self.direction, + payload=data, + created_at=created_at, + ) + + if not self.validate(data): + record.status = SyncStatus.FAILED + record.error_message = "数据校验失败" + return record + + try: + transformed = self.transform_to_local(data) + except Exception as e: + record.status = SyncStatus.FAILED + record.error_message = f"数据转换失败: {e}" + return record + + try: + success = self.save_to_local(transformed) + record.status = SyncStatus.SUCCESS if success else SyncStatus.FAILED + record.external_id = transformed.get("external_id", "") + if not success: + record.error_message = record.error_message or "保存失败" + except Exception as e: + record.status = SyncStatus.FAILED + record.error_message = f"保存失败: {e}" + + return record diff --git a/backend/plugins/eip_sync/sync/clients/__init__.py b/backend/plugins/eip_sync/sync/clients/__init__.py new file mode 100644 index 0000000..ed4de40 --- /dev/null +++ b/backend/plugins/eip_sync/sync/clients/__init__.py @@ -0,0 +1,3 @@ +from sync.clients.eip_client import EipClient + +__all__ = ["EipClient"] diff --git a/backend/plugins/eip_sync/sync/clients/eip_client.py b/backend/plugins/eip_sync/sync/clients/eip_client.py new file mode 100644 index 0000000..ff6ebcf --- /dev/null +++ b/backend/plugins/eip_sync/sync/clients/eip_client.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import Any, Dict, Optional + +import requests +from django.conf import settings + + +class EipClient: + """HTTP client for PIS -> EIP outbound calls (placeholder for future use).""" + + def __init__( + self, + base_url: Optional[str] = None, + api_key: Optional[str] = None, + timeout: int = 30, + ): + self.base_url = (base_url or getattr(settings, "EIP_BASE_URL", "") or "").rstrip("/") + self.api_key = api_key or getattr(settings, "EIP_API_KEY", "") or "" + self.timeout = timeout + self.session = requests.Session() + if self.api_key: + self.session.headers.update( + { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}", + } + ) + + def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: + url = f"{self.base_url}{endpoint}" + try: + response = self.session.post(url, json=data, timeout=self.timeout) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + return {"Status": "fail", "Message": str(e)} + + def get(self, endpoint: str, params: Dict[str, Any] | None = None) -> Dict[str, Any]: + url = f"{self.base_url}{endpoint}" + try: + response = self.session.get(url, params=params or {}, timeout=self.timeout) + response.raise_for_status() + return response.json() + except requests.RequestException as e: + return {"Status": "fail", "Message": str(e)} diff --git a/backend/plugins/eip_sync/sync/factory.py b/backend/plugins/eip_sync/sync/factory.py new file mode 100644 index 0000000..47f2e67 --- /dev/null +++ b/backend/plugins/eip_sync/sync/factory.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import Any, Dict, Type + +from sync.base import BaseSyncAdapter + + +class SyncFactory: + """Creates sync adapters by registered name.""" + + _adapters: Dict[str, Type[BaseSyncAdapter]] = {} + + @classmethod + def register(cls, name: str, adapter_class: Type[BaseSyncAdapter]) -> None: + cls._adapters[name] = adapter_class + + @classmethod + def create(cls, name: str, **kwargs: Any) -> BaseSyncAdapter: + if name not in cls._adapters: + raise ValueError(f"未注册的同步适配器: {name}") + return cls._adapters[name](**kwargs) + + @classmethod + def list_adapters(cls) -> list: + return list(cls._adapters.keys()) diff --git a/backend/plugins/eip_sync/sync/logger.py b/backend/plugins/eip_sync/sync/logger.py new file mode 100644 index 0000000..d52250e --- /dev/null +++ b/backend/plugins/eip_sync/sync/logger.py @@ -0,0 +1,23 @@ +import logging +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sync.base import SyncOperationResult + + +class SyncLogger: + def __init__(self, name: str = "pis.sync"): + self.logger = logging.getLogger(name) + + def log_sync(self, record: "SyncOperationResult", level: str = "INFO") -> None: + message = ( + f"[{record.adapter_name}] direction={record.direction.value} " + f"status={record.status.value} external_id={record.external_id} " + f"error={record.error_message or 'none'}" + ) + if level == "ERROR": + self.logger.error(message) + elif level == "WARNING": + self.logger.warning(message) + else: + self.logger.info(message) diff --git a/backend/plugins/eip_sync/sync/manager.py b/backend/plugins/eip_sync/sync/manager.py new file mode 100644 index 0000000..07bb3df --- /dev/null +++ b/backend/plugins/eip_sync/sync/manager.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from django.utils import timezone + +from sync.base import SyncOperationResult, SyncStatus +from sync.factory import SyncFactory +from sync.logger import SyncLogger +from sync.models import SyncRecord + + +class SyncManager: + """Routes inbound EIP webhooks to registered adapters.""" + + SUCCESS_MESSAGE_MISC = "物料信息已成功抛转至PIS" + + def __init__(self): + self.logger = SyncLogger() + + def process_eip_webhook(self, adapter_name: str, data: Dict[str, Any]) -> Dict[str, Any]: + if adapter_name not in SyncFactory.list_adapters(): + return {"Status": "fail", "Message": f"未知的适配器: {adapter_name}"} + + adapter = SyncFactory.create(adapter_name) + op = adapter.push_to_local(data) + self._persist_record(op) + self.logger.log_sync(op) + + if op.status == SyncStatus.SUCCESS: + message = ( + self.SUCCESS_MESSAGE_MISC if adapter_name == "misc_material" else "同步成功" + ) + return {"Status": "success", "Message": message} + + return {"Status": "fail", "Message": op.error_message or "同步失败"} + + def _persist_record(self, op: SyncOperationResult) -> None: + completed_at = timezone.now() + SyncRecord.objects.create( + adapter_name=op.adapter_name, + direction=op.direction.value, + external_id=op.external_id or "", + payload=op.payload, + response=op.response, + status=op.status.value, + error_message=op.error_message or "", + retry_count=op.retry_count, + completed_at=completed_at, + ) + + def get_sync_history( + self, + adapter_name: Optional[str] = None, + direction: Optional[str] = None, + status: Optional[str] = None, + limit: int = 100, + ) -> List[SyncRecord]: + qs = SyncRecord.objects.all() + if adapter_name: + qs = qs.filter(adapter_name=adapter_name) + if direction: + qs = qs.filter(direction=direction) + if status: + qs = qs.filter(status=status) + return list(qs[:limit]) diff --git a/backend/plugins/eip_sync/sync/models.py b/backend/plugins/eip_sync/sync/models.py new file mode 100644 index 0000000..bf7bb39 --- /dev/null +++ b/backend/plugins/eip_sync/sync/models.py @@ -0,0 +1,50 @@ +from django.db import models + +from dvadmin.utils.models import CoreModel, table_prefix + + +class SyncRecord(CoreModel): + """Inbound/outbound sync audit row.""" + + SYNC_DIRECTION_CHOICES = ( + ("eip_to_pis", "EIP to PIS"), + ("pis_to_eip", "PIS to EIP"), + ) + + SYNC_STATUS_CHOICES = ( + ("pending", "待处理"), + ("success", "成功"), + ("failed", "失败"), + ("retrying", "重试中"), + ) + + adapter_name = models.CharField(max_length=50, verbose_name="适配器名称", db_index=True) + direction = models.CharField(max_length=20, choices=SYNC_DIRECTION_CHOICES, verbose_name="同步方向") + external_id = models.CharField(max_length=100, blank=True, verbose_name="外部单号", db_index=True) + payload = models.JSONField(null=True, blank=True, verbose_name="请求数据") + response = models.JSONField(null=True, blank=True, verbose_name="响应数据") + status = models.CharField( + max_length=20, + choices=SYNC_STATUS_CHOICES, + default="pending", + verbose_name="状态", + db_index=True, + ) + error_message = models.TextField(blank=True, verbose_name="错误信息") + retry_count = models.IntegerField(default=0, verbose_name="重试次数") + retry_max = models.IntegerField(default=3, verbose_name="最大重试次数") + completed_at = models.DateTimeField(null=True, blank=True, verbose_name="完成时间") + + class Meta: + db_table = table_prefix + "sync_record" + verbose_name = "数据同步记录" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + indexes = [ + models.Index(fields=["adapter_name", "status"]), + models.Index(fields=["external_id"]), + models.Index(fields=["create_datetime"]), + ] + + def __str__(self) -> str: + return f"{self.adapter_name} - {self.direction} - {self.status}" diff --git a/backend/plugins/eip_sync/sync/urls.py b/backend/plugins/eip_sync/sync/urls.py new file mode 100644 index 0000000..f40e14d --- /dev/null +++ b/backend/plugins/eip_sync/sync/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from sync.views import MiscMaterialSyncView + +urlpatterns = [ + path("misc-material/", MiscMaterialSyncView.as_view(), name="sync-misc-material"), +] diff --git a/backend/plugins/eip_sync/sync/views.py b/backend/plugins/eip_sync/sync/views.py new file mode 100644 index 0000000..4e26f56 --- /dev/null +++ b/backend/plugins/eip_sync/sync/views.py @@ -0,0 +1,23 @@ +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from dvadmin_ak_sk.libs.authentication import AkSkAuthentication + +from sync.manager import SyncManager + + +class MiscMaterialSyncView(APIView): + """ + EIP -> PIS: miscellaneous procurement material master (杂采料号). + Authenticated via dvadmin-ak-sk (X-NSF-* signature headers). + """ + + authentication_classes = [AkSkAuthentication] + permission_classes = [IsAuthenticated] + + def post(self, request, *args, **kwargs): + manager = SyncManager() + result = manager.process_eip_webhook("misc_material", request.data) + status_code = 200 if result.get("Status") == "success" else 400 + return Response(result, status=status_code) diff --git a/backend/plugins/test/__init__.py b/backend/plugins/test/__init__.py new file mode 100644 index 0000000..df93c36 --- /dev/null +++ b/backend/plugins/test/__init__.py @@ -0,0 +1 @@ +# Plugin tests (EIP sync, etc.) diff --git a/backend/plugins/test/test_sync_misc_material.py b/backend/plugins/test/test_sync_misc_material.py new file mode 100644 index 0000000..cd42275 --- /dev/null +++ b/backend/plugins/test/test_sync_misc_material.py @@ -0,0 +1,184 @@ +""" +杂采料号 EIP 同步:适配器、SyncManager、HTTP 接口。 + +使用 pytest + django_db,客户端与 fixtures 与项目 conftest 一致(api_client / authenticate)。 +""" +import pytest +from rest_framework import status + +from apps.pisadmin.miscprocurement.models import MiscProcMaterial +from sync.adapters.misc_material import MiscMaterialSyncAdapter +from sync.base import SyncStatus +from sync.factory import SyncFactory +from sync.manager import SyncManager +from sync.models import SyncRecord + + +@pytest.mark.django_db +class TestMiscMaterialSyncAdapter: + """MiscMaterialSyncAdapter:校验、转换、落库。""" + + def test_validate_requires_company_and_material(self): + adapter = MiscMaterialSyncAdapter() + assert adapter.validate({}) is False + assert adapter.validate({"companyCode": "A01"}) is False + assert adapter.validate({"materialCode": "P1"}) is False + assert adapter.validate({"companyCode": "A01", "materialCode": "P1"}) is True + assert adapter.validate({"company_code": "A01", "material_code": "P1"}) is True + + def test_transform_maps_eip_fields(self): + adapter = MiscMaterialSyncAdapter() + data = { + "companyCode": "VC01", + "materialCode": "M-001", + "materialName": "说明A", + "specification": "SPEC-1", + "unit": "PCS", + "Category": "2", + "accountCode": "6001", + } + t = adapter.transform_to_local(data) + assert t["company_code"] == "VC01" + assert t["partid"] == "M-001" + assert t["partid_name"] == "说明A" + assert t["specification"] == "SPEC-1" + assert t["unit"] == "PCS" + assert t["partid_category_id"] == 2 + assert t["external_id"] == "VC01_M-001" + + def test_transform_non_numeric_category_goes_to_description(self): + adapter = MiscMaterialSyncAdapter() + data = { + "companyCode": "VC01", + "materialCode": "M-002", + "Category": "模治具", + "accountCode": "ACC1", + } + t = adapter.transform_to_local(data) + assert t["partid_category_id"] is None + assert "模治具" in (t.get("description") or "") + assert "ACC1" in (t.get("description") or "") + + def test_push_to_local_creates_misc_proc_material(self): + adapter = MiscMaterialSyncAdapter() + payload = { + "companyCode": "VC01", + "materialCode": "NEW-PART", + "materialNameZh": "新料号", + "specification": "S", + "unit": "EA", + } + op = adapter.push_to_local(payload) + assert op.status == SyncStatus.SUCCESS + obj = MiscProcMaterial.objects.get(company_code="VC01", partid="NEW-PART") + assert obj.partid_name == "新料号" + assert obj.specification == "S" + assert obj.unit == "EA" + + def test_push_to_local_update_existing(self): + adapter = MiscMaterialSyncAdapter() + MiscProcMaterial.objects.create( + company_code="VC01", + partid="UP-1", + partid_name="旧名", + specification="old", + unit="U", + ) + op = adapter.push_to_local( + { + "companyCode": "VC01", + "materialCode": "UP-1", + "materialName": "新名", + "specification": "new", + "unit": "U", + } + ) + assert op.status == SyncStatus.SUCCESS + obj = MiscProcMaterial.objects.get(company_code="VC01", partid="UP-1") + assert obj.partid_name == "新名" + assert obj.specification == "new" + + +@pytest.mark.django_db +class TestSyncManager: + """SyncManager.process_eip_webhook""" + + def test_unknown_adapter(self): + mgr = SyncManager() + out = mgr.process_eip_webhook("no_such_adapter", {}) + assert out["Status"] == "fail" + assert "未知" in out["Message"] + + def test_success_writes_sync_record(self): + mgr = SyncManager() + payload = { + "companyCode": "VC01", + "materialCode": "SR-1", + "materialName": "记录测试", + "specification": "", + "unit": "", + } + out = mgr.process_eip_webhook("misc_material", payload) + assert out["Status"] == "success" + assert out["Message"] == mgr.SUCCESS_MESSAGE_MISC + assert SyncRecord.objects.filter(adapter_name="misc_material", status="success").exists() + + def test_validation_failure_writes_failed_record(self): + mgr = SyncManager() + out = mgr.process_eip_webhook("misc_material", {"companyCode": "X"}) + assert out["Status"] == "fail" + assert SyncRecord.objects.filter(adapter_name="misc_material", status="failed").exists() + + +@pytest.mark.django_db +class TestMiscMaterialSyncAPI: + """POST /api/pisadmin/sync/misc-material/(DRF force_authenticate 绕过 AK/SK 验签,仅测业务链)。""" + + URL = "/api/pisadmin/sync/misc-material/" + + def test_post_success(self, api_client, admin_user): + api_client.force_authenticate(user=admin_user) + payload = { + "companyCode": "VC01", + "materialCode": "API-001", + "materialName": "接口测", + "specification": "SP", + "unit": "KG", + } + response = api_client.post(self.URL, data=payload, format="json") + assert response.status_code == status.HTTP_200_OK + assert response.data["Status"] == "success" + assert "物料信息已成功抛转至PIS" in response.data["Message"] + assert MiscProcMaterial.objects.filter(partid="API-001").exists() + + def test_post_validation_error(self, api_client, admin_user): + api_client.force_authenticate(user=admin_user) + response = api_client.post( + self.URL, data={"companyCode": ""}, format="json" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["Status"] == "fail" + + def test_post_unauthenticated(self, api_client): + response = api_client.post( + self.URL, + data={ + "companyCode": "VC01", + "materialCode": "X", + "materialName": "n", + "specification": "", + "unit": "", + }, + format="json", + ) + assert response.status_code in ( + status.HTTP_401_UNAUTHORIZED, + status.HTTP_403_FORBIDDEN, + ) + + +@pytest.mark.django_db +def test_sync_factory_registers_misc_material(): + assert "misc_material" in SyncFactory.list_adapters() + adapter = SyncFactory.create("misc_material") + assert isinstance(adapter, MiscMaterialSyncAdapter) diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..1c5bece --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +DJANGO_SETTINGS_MODULE = application.test_settings +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --tb=short +testpaths = apps tests plugins diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..e6e318d --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,39 @@ +setuptools>=69.0.0,<70 +Django==4.2.14 +django-comment-migrate==0.1.7 +django-cors-headers==4.4.0 +django-filter==24.2 +django-ranged-response==0.2.0 +djangorestframework==3.15.2 +django-restql==0.15.4 +django-simple-captcha==0.6.0 +django-timezone-field==7.0 +djangorestframework_simplejwt==5.4.0 +drf-yasg==1.21.7 +mssql-django==1.7 +pypinyin==0.51.0 +ua-parser==0.18.0 +pyparsing==3.1.2 +openpyxl==3.1.5 +requests==2.32.4 +typing-extensions==4.12.2 +tzlocal==5.2 +channels==4.1.0 +channels-redis==4.2.0 +user-agents==2.2.0 +six==1.16.0 +whitenoise==6.7.0 +uvicorn==0.30.3 +gunicorn==23.0.0 +gevent==24.2.1 +Pillow==10.4.0 +pyinstaller==6.9.0 +dvadmin3-celery==3.1.6 +boto3>=1.34.0 +pytest-django>=4.5.0 +factory_boy>=3.3.0 +dvadmin-ak-sk==1.0.0 + +# Git hooks +pre-commit>=3.0.0 +ruff>=0.9.0 \ No newline at end of file diff --git a/backend/static/captcha/fonts/COPYRIGHT.TXT b/backend/static/captcha/fonts/COPYRIGHT.TXT new file mode 100644 index 0000000..bb7523a --- /dev/null +++ b/backend/static/captcha/fonts/COPYRIGHT.TXT @@ -0,0 +1,123 @@ +Bitstream Vera Fonts Copyright + +The fonts have a generous copyright, allowing derivative works (as +long as "Bitstream" or "Vera" are not in the names), and full +redistribution (so long as they are not *sold* by themselves). They +can be be bundled, redistributed and sold with any software. + +The fonts are distributed under the following copyright: + +Copyright +========= + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream +Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute +the Font Software, including without limitation the rights to use, +copy, merge, publish, distribute, and/or sell copies of the Font +Software, and to permit persons to whom the Font Software is furnished +to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font +Software without prior written authorization from the Gnome Foundation +or Bitstream Inc., respectively. For further information, contact: +fonts at gnome dot org. + +Copyright FAQ +============= + + 1. I don't understand the resale restriction... What gives? + + Bitstream is giving away these fonts, but wishes to ensure its + competitors can't just drop the fonts as is into a font sale system + and sell them as is. It seems fair that if Bitstream can't make money + from the Bitstream Vera fonts, their competitors should not be able to + do so either. You can sell the fonts as part of any software package, + however. + + 2. I want to package these fonts separately for distribution and + sale as part of a larger software package or system. Can I do so? + + Yes. A RPM or Debian package is a "larger software package" to begin + with, and you aren't selling them independently by themselves. + See 1. above. + + 3. Are derivative works allowed? + Yes! + + 4. Can I change or add to the font(s)? + Yes, but you must change the name(s) of the font(s). + + 5. Under what terms are derivative works allowed? + + You must change the name(s) of the fonts. This is to ensure the + quality of the fonts, both to protect Bitstream and Gnome. We want to + ensure that if an application has opened a font specifically of these + names, it gets what it expects (though of course, using fontconfig, + substitutions could still could have occurred during font + opening). You must include the Bitstream copyright. Additional + copyrights can be added, as per copyright law. Happy Font Hacking! + + 6. If I have improvements for Bitstream Vera, is it possible they might get + adopted in future versions? + + Yes. The contract between the Gnome Foundation and Bitstream has + provisions for working with Bitstream to ensure quality additions to + the Bitstream Vera font family. Please contact us if you have such + additions. Note, that in general, we will want such additions for the + entire family, not just a single font, and that you'll have to keep + both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add + glyphs to the font, they must be stylistically in keeping with Vera's + design. Vera cannot become a "ransom note" font. Jim Lyles will be + providing a document describing the design elements used in Vera, as a + guide and aid for people interested in contributing to Vera. + + 7. I want to sell a software package that uses these fonts: Can I do so? + + Sure. Bundle the fonts with your software and sell your software + with the fonts. That is the intent of the copyright. + + 8. If applications have built the names "Bitstream Vera" into them, + can I override this somehow to use fonts of my choosing? + + This depends on exact details of the software. Most open source + systems and software (e.g., Gnome, KDE, etc.) are now converting to + use fontconfig (see www.fontconfig.org) to handle font configuration, + selection and substitution; it has provisions for overriding font + names and substituting alternatives. An example is provided by the + supplied local.conf file, which chooses the family Bitstream Vera for + "sans", "serif" and "monospace". Other software (e.g., the XFree86 + core server) has other mechanisms for font substitution. diff --git a/backend/static/captcha/fonts/README.TXT b/backend/static/captcha/fonts/README.TXT new file mode 100644 index 0000000..1cf37bd --- /dev/null +++ b/backend/static/captcha/fonts/README.TXT @@ -0,0 +1,11 @@ +Contained herin is the Bitstream Vera font family. + +The Copyright information is found in the COPYRIGHT.TXT file (along +with being incorporated into the fonts themselves). + +The releases notes are found in the file "RELEASENOTES.TXT". + +We hope you enjoy Vera! + + Bitstream, Inc. + The Gnome Project diff --git a/backend/static/captcha/fonts/Vera.ttf b/backend/static/captcha/fonts/Vera.ttf new file mode 100644 index 0000000..58cd6b5 Binary files /dev/null and b/backend/static/captcha/fonts/Vera.ttf differ diff --git a/backend/static/clause/privacy.css b/backend/static/clause/privacy.css new file mode 100644 index 0000000..19ff57e --- /dev/null +++ b/backend/static/clause/privacy.css @@ -0,0 +1,34 @@ +html,body{width:100%;overflow-x: hidden;margin: 0;padding: 0;font-size:1rem;font-family: "PingFang SC","SF Pro SC","SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif; + -webkit-font-smoothing: antialiased;} +a{text-decoration:none;} +ul,li{list-style:none;padding:0;margin:0;} +.bg{background-repeat:no-repeat;background-position:center center;background-size:100%;} +.center{margin:0 auto;position:relative;} +.abs_center{position:absolute;left: 50%;transform: translateX(-50%);-webkit-transform:translateX(-50%);} +/*滚动条统一样式*/ +.scrollbar{overflow-x:hidden;scrollbar-track-color:none;} +.scrollbar::-webkit-scrollbar{width:8px;background-color:transparent;} /*滚动条整体部分*/ +.scrollbar::-webkit-scrollbar-track{border-radius:4px;-webkit-box-shadow: inset 0 0 0 rgba(0,0,0,0);background-color:transparent;display:none;} /* 滚动条的轨道*/ +.scrollbar::-webkit-scrollbar-track-piece{background-color:transparent;display:none;}/* 滚动条的内层轨道*/ +.scrollbar::-webkit-scrollbar-thumb{border-radius:4px;background: #313131;}/*滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条)*/ + +h3,h4,h5{ font-weight: bold;} +h3{ font-size: .4rem; color:#000;} +h4{ font-size: .35rem; color: #000; margin:.2rem 0 0 0;} +p{ line-height: .5rem; margin:.2rem 0;word-break: break-all;text-align:justify;} +.app .main{width:90%;overflow:hidden;font-size:.30rem;box-sizing: border-box; margin: 0 auto; color: #484d57;} +.app ul { padding-left: .1rem; font-size: .26rem;} +.app ul p{ line-height: .4rem;} +.app .mainbg{position:absolute;left:50%;transform:translateX(-50%);background-repeat:no-repeat;background-size:100%;} + +.app .poster{width:100%;height:31.85rem;background-image:url(../images/poster_bg.jpg);} + +.pc .main{font-size:.16rem;} + + + + + + + + diff --git a/backend/static/drf-yasg/README b/backend/static/drf-yasg/README new file mode 100644 index 0000000..eebf91a --- /dev/null +++ b/backend/static/drf-yasg/README @@ -0,0 +1,18 @@ +Information about external resources + +The following files are taken from external resources or trees. + +Files: insQ.js + insQ.min.js +License: MIT +Copyright: Zbyszek Tenerowicz + Eryk Napierała + Askar Yusupov + Dan Dascalescu +Source: https://github.com/naugtur/insertionQuery v1.0.3 + +Files: immutable.js + immutable.min.js +License: MIT +Copyright: 2014-present, Facebook, Inc +Source: https://github.com/immutable-js/immutable-js/releases/tag/v3.8.2 diff --git a/backend/static/drf-yasg/README.gz b/backend/static/drf-yasg/README.gz new file mode 100644 index 0000000..9b8e0e4 Binary files /dev/null and b/backend/static/drf-yasg/README.gz differ diff --git a/backend/static/drf-yasg/immutable.js b/backend/static/drf-yasg/immutable.js new file mode 100644 index 0000000..0e2ac97 --- /dev/null +++ b/backend/static/drf-yasg/immutable.js @@ -0,0 +1,4977 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Immutable = factory()); +}(this, function () { 'use strict';var SLICE$0 = Array.prototype.slice; + + function createClass(ctor, superClass) { + if (superClass) { + ctor.prototype = Object.create(superClass.prototype); + } + ctor.prototype.constructor = ctor; + } + + function Iterable(value) { + return isIterable(value) ? value : Seq(value); + } + + + createClass(KeyedIterable, Iterable); + function KeyedIterable(value) { + return isKeyed(value) ? value : KeyedSeq(value); + } + + + createClass(IndexedIterable, Iterable); + function IndexedIterable(value) { + return isIndexed(value) ? value : IndexedSeq(value); + } + + + createClass(SetIterable, Iterable); + function SetIterable(value) { + return isIterable(value) && !isAssociative(value) ? value : SetSeq(value); + } + + + + function isIterable(maybeIterable) { + return !!(maybeIterable && maybeIterable[IS_ITERABLE_SENTINEL]); + } + + function isKeyed(maybeKeyed) { + return !!(maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL]); + } + + function isIndexed(maybeIndexed) { + return !!(maybeIndexed && maybeIndexed[IS_INDEXED_SENTINEL]); + } + + function isAssociative(maybeAssociative) { + return isKeyed(maybeAssociative) || isIndexed(maybeAssociative); + } + + function isOrdered(maybeOrdered) { + return !!(maybeOrdered && maybeOrdered[IS_ORDERED_SENTINEL]); + } + + Iterable.isIterable = isIterable; + Iterable.isKeyed = isKeyed; + Iterable.isIndexed = isIndexed; + Iterable.isAssociative = isAssociative; + Iterable.isOrdered = isOrdered; + + Iterable.Keyed = KeyedIterable; + Iterable.Indexed = IndexedIterable; + Iterable.Set = SetIterable; + + + var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@'; + var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@'; + var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@'; + var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@'; + + // Used for setting prototype methods that IE8 chokes on. + var DELETE = 'delete'; + + // Constants describing the size of trie nodes. + var SHIFT = 5; // Resulted in best performance after ______? + var SIZE = 1 << SHIFT; + var MASK = SIZE - 1; + + // A consistent shared value representing "not set" which equals nothing other + // than itself, and nothing that could be provided externally. + var NOT_SET = {}; + + // Boolean references, Rough equivalent of `bool &`. + var CHANGE_LENGTH = { value: false }; + var DID_ALTER = { value: false }; + + function MakeRef(ref) { + ref.value = false; + return ref; + } + + function SetRef(ref) { + ref && (ref.value = true); + } + + // A function which returns a value representing an "owner" for transient writes + // to tries. The return value will only ever equal itself, and will not equal + // the return of any subsequent call of this function. + function OwnerID() {} + + // http://jsperf.com/copy-array-inline + function arrCopy(arr, offset) { + offset = offset || 0; + var len = Math.max(0, arr.length - offset); + var newArr = new Array(len); + for (var ii = 0; ii < len; ii++) { + newArr[ii] = arr[ii + offset]; + } + return newArr; + } + + function ensureSize(iter) { + if (iter.size === undefined) { + iter.size = iter.__iterate(returnTrue); + } + return iter.size; + } + + function wrapIndex(iter, index) { + // This implements "is array index" which the ECMAString spec defines as: + // + // A String property name P is an array index if and only if + // ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal + // to 2^32−1. + // + // http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects + if (typeof index !== 'number') { + var uint32Index = index >>> 0; // N >>> 0 is shorthand for ToUint32 + if ('' + uint32Index !== index || uint32Index === 4294967295) { + return NaN; + } + index = uint32Index; + } + return index < 0 ? ensureSize(iter) + index : index; + } + + function returnTrue() { + return true; + } + + function wholeSlice(begin, end, size) { + return (begin === 0 || (size !== undefined && begin <= -size)) && + (end === undefined || (size !== undefined && end >= size)); + } + + function resolveBegin(begin, size) { + return resolveIndex(begin, size, 0); + } + + function resolveEnd(end, size) { + return resolveIndex(end, size, size); + } + + function resolveIndex(index, size, defaultIndex) { + return index === undefined ? + defaultIndex : + index < 0 ? + Math.max(0, size + index) : + size === undefined ? + index : + Math.min(size, index); + } + + /* global Symbol */ + + var ITERATE_KEYS = 0; + var ITERATE_VALUES = 1; + var ITERATE_ENTRIES = 2; + + var REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; + var FAUX_ITERATOR_SYMBOL = '@@iterator'; + + var ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL; + + + function Iterator(next) { + this.next = next; + } + + Iterator.prototype.toString = function() { + return '[Iterator]'; + }; + + + Iterator.KEYS = ITERATE_KEYS; + Iterator.VALUES = ITERATE_VALUES; + Iterator.ENTRIES = ITERATE_ENTRIES; + + Iterator.prototype.inspect = + Iterator.prototype.toSource = function () { return this.toString(); } + Iterator.prototype[ITERATOR_SYMBOL] = function () { + return this; + }; + + + function iteratorValue(type, k, v, iteratorResult) { + var value = type === 0 ? k : type === 1 ? v : [k, v]; + iteratorResult ? (iteratorResult.value = value) : (iteratorResult = { + value: value, done: false + }); + return iteratorResult; + } + + function iteratorDone() { + return { value: undefined, done: true }; + } + + function hasIterator(maybeIterable) { + return !!getIteratorFn(maybeIterable); + } + + function isIterator(maybeIterator) { + return maybeIterator && typeof maybeIterator.next === 'function'; + } + + function getIterator(iterable) { + var iteratorFn = getIteratorFn(iterable); + return iteratorFn && iteratorFn.call(iterable); + } + + function getIteratorFn(iterable) { + var iteratorFn = iterable && ( + (REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) || + iterable[FAUX_ITERATOR_SYMBOL] + ); + if (typeof iteratorFn === 'function') { + return iteratorFn; + } + } + + function isArrayLike(value) { + return value && typeof value.length === 'number'; + } + + createClass(Seq, Iterable); + function Seq(value) { + return value === null || value === undefined ? emptySequence() : + isIterable(value) ? value.toSeq() : seqFromValue(value); + } + + Seq.of = function(/*...values*/) { + return Seq(arguments); + }; + + Seq.prototype.toSeq = function() { + return this; + }; + + Seq.prototype.toString = function() { + return this.__toString('Seq {', '}'); + }; + + Seq.prototype.cacheResult = function() { + if (!this._cache && this.__iterateUncached) { + this._cache = this.entrySeq().toArray(); + this.size = this._cache.length; + } + return this; + }; + + // abstract __iterateUncached(fn, reverse) + + Seq.prototype.__iterate = function(fn, reverse) { + return seqIterate(this, fn, reverse, true); + }; + + // abstract __iteratorUncached(type, reverse) + + Seq.prototype.__iterator = function(type, reverse) { + return seqIterator(this, type, reverse, true); + }; + + + + createClass(KeyedSeq, Seq); + function KeyedSeq(value) { + return value === null || value === undefined ? + emptySequence().toKeyedSeq() : + isIterable(value) ? + (isKeyed(value) ? value.toSeq() : value.fromEntrySeq()) : + keyedSeqFromValue(value); + } + + KeyedSeq.prototype.toKeyedSeq = function() { + return this; + }; + + + + createClass(IndexedSeq, Seq); + function IndexedSeq(value) { + return value === null || value === undefined ? emptySequence() : + !isIterable(value) ? indexedSeqFromValue(value) : + isKeyed(value) ? value.entrySeq() : value.toIndexedSeq(); + } + + IndexedSeq.of = function(/*...values*/) { + return IndexedSeq(arguments); + }; + + IndexedSeq.prototype.toIndexedSeq = function() { + return this; + }; + + IndexedSeq.prototype.toString = function() { + return this.__toString('Seq [', ']'); + }; + + IndexedSeq.prototype.__iterate = function(fn, reverse) { + return seqIterate(this, fn, reverse, false); + }; + + IndexedSeq.prototype.__iterator = function(type, reverse) { + return seqIterator(this, type, reverse, false); + }; + + + + createClass(SetSeq, Seq); + function SetSeq(value) { + return ( + value === null || value === undefined ? emptySequence() : + !isIterable(value) ? indexedSeqFromValue(value) : + isKeyed(value) ? value.entrySeq() : value + ).toSetSeq(); + } + + SetSeq.of = function(/*...values*/) { + return SetSeq(arguments); + }; + + SetSeq.prototype.toSetSeq = function() { + return this; + }; + + + + Seq.isSeq = isSeq; + Seq.Keyed = KeyedSeq; + Seq.Set = SetSeq; + Seq.Indexed = IndexedSeq; + + var IS_SEQ_SENTINEL = '@@__IMMUTABLE_SEQ__@@'; + + Seq.prototype[IS_SEQ_SENTINEL] = true; + + + + createClass(ArraySeq, IndexedSeq); + function ArraySeq(array) { + this._array = array; + this.size = array.length; + } + + ArraySeq.prototype.get = function(index, notSetValue) { + return this.has(index) ? this._array[wrapIndex(this, index)] : notSetValue; + }; + + ArraySeq.prototype.__iterate = function(fn, reverse) { + var array = this._array; + var maxIndex = array.length - 1; + for (var ii = 0; ii <= maxIndex; ii++) { + if (fn(array[reverse ? maxIndex - ii : ii], ii, this) === false) { + return ii + 1; + } + } + return ii; + }; + + ArraySeq.prototype.__iterator = function(type, reverse) { + var array = this._array; + var maxIndex = array.length - 1; + var ii = 0; + return new Iterator(function() + {return ii > maxIndex ? + iteratorDone() : + iteratorValue(type, ii, array[reverse ? maxIndex - ii++ : ii++])} + ); + }; + + + + createClass(ObjectSeq, KeyedSeq); + function ObjectSeq(object) { + var keys = Object.keys(object); + this._object = object; + this._keys = keys; + this.size = keys.length; + } + + ObjectSeq.prototype.get = function(key, notSetValue) { + if (notSetValue !== undefined && !this.has(key)) { + return notSetValue; + } + return this._object[key]; + }; + + ObjectSeq.prototype.has = function(key) { + return this._object.hasOwnProperty(key); + }; + + ObjectSeq.prototype.__iterate = function(fn, reverse) { + var object = this._object; + var keys = this._keys; + var maxIndex = keys.length - 1; + for (var ii = 0; ii <= maxIndex; ii++) { + var key = keys[reverse ? maxIndex - ii : ii]; + if (fn(object[key], key, this) === false) { + return ii + 1; + } + } + return ii; + }; + + ObjectSeq.prototype.__iterator = function(type, reverse) { + var object = this._object; + var keys = this._keys; + var maxIndex = keys.length - 1; + var ii = 0; + return new Iterator(function() { + var key = keys[reverse ? maxIndex - ii : ii]; + return ii++ > maxIndex ? + iteratorDone() : + iteratorValue(type, key, object[key]); + }); + }; + + ObjectSeq.prototype[IS_ORDERED_SENTINEL] = true; + + + createClass(IterableSeq, IndexedSeq); + function IterableSeq(iterable) { + this._iterable = iterable; + this.size = iterable.length || iterable.size; + } + + IterableSeq.prototype.__iterateUncached = function(fn, reverse) { + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + var iterable = this._iterable; + var iterator = getIterator(iterable); + var iterations = 0; + if (isIterator(iterator)) { + var step; + while (!(step = iterator.next()).done) { + if (fn(step.value, iterations++, this) === false) { + break; + } + } + } + return iterations; + }; + + IterableSeq.prototype.__iteratorUncached = function(type, reverse) { + if (reverse) { + return this.cacheResult().__iterator(type, reverse); + } + var iterable = this._iterable; + var iterator = getIterator(iterable); + if (!isIterator(iterator)) { + return new Iterator(iteratorDone); + } + var iterations = 0; + return new Iterator(function() { + var step = iterator.next(); + return step.done ? step : iteratorValue(type, iterations++, step.value); + }); + }; + + + + createClass(IteratorSeq, IndexedSeq); + function IteratorSeq(iterator) { + this._iterator = iterator; + this._iteratorCache = []; + } + + IteratorSeq.prototype.__iterateUncached = function(fn, reverse) { + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + var iterator = this._iterator; + var cache = this._iteratorCache; + var iterations = 0; + while (iterations < cache.length) { + if (fn(cache[iterations], iterations++, this) === false) { + return iterations; + } + } + var step; + while (!(step = iterator.next()).done) { + var val = step.value; + cache[iterations] = val; + if (fn(val, iterations++, this) === false) { + break; + } + } + return iterations; + }; + + IteratorSeq.prototype.__iteratorUncached = function(type, reverse) { + if (reverse) { + return this.cacheResult().__iterator(type, reverse); + } + var iterator = this._iterator; + var cache = this._iteratorCache; + var iterations = 0; + return new Iterator(function() { + if (iterations >= cache.length) { + var step = iterator.next(); + if (step.done) { + return step; + } + cache[iterations] = step.value; + } + return iteratorValue(type, iterations, cache[iterations++]); + }); + }; + + + + + // # pragma Helper functions + + function isSeq(maybeSeq) { + return !!(maybeSeq && maybeSeq[IS_SEQ_SENTINEL]); + } + + var EMPTY_SEQ; + + function emptySequence() { + return EMPTY_SEQ || (EMPTY_SEQ = new ArraySeq([])); + } + + function keyedSeqFromValue(value) { + var seq = + Array.isArray(value) ? new ArraySeq(value).fromEntrySeq() : + isIterator(value) ? new IteratorSeq(value).fromEntrySeq() : + hasIterator(value) ? new IterableSeq(value).fromEntrySeq() : + typeof value === 'object' ? new ObjectSeq(value) : + undefined; + if (!seq) { + throw new TypeError( + 'Expected Array or iterable object of [k, v] entries, '+ + 'or keyed object: ' + value + ); + } + return seq; + } + + function indexedSeqFromValue(value) { + var seq = maybeIndexedSeqFromValue(value); + if (!seq) { + throw new TypeError( + 'Expected Array or iterable object of values: ' + value + ); + } + return seq; + } + + function seqFromValue(value) { + var seq = maybeIndexedSeqFromValue(value) || + (typeof value === 'object' && new ObjectSeq(value)); + if (!seq) { + throw new TypeError( + 'Expected Array or iterable object of values, or keyed object: ' + value + ); + } + return seq; + } + + function maybeIndexedSeqFromValue(value) { + return ( + isArrayLike(value) ? new ArraySeq(value) : + isIterator(value) ? new IteratorSeq(value) : + hasIterator(value) ? new IterableSeq(value) : + undefined + ); + } + + function seqIterate(seq, fn, reverse, useKeys) { + var cache = seq._cache; + if (cache) { + var maxIndex = cache.length - 1; + for (var ii = 0; ii <= maxIndex; ii++) { + var entry = cache[reverse ? maxIndex - ii : ii]; + if (fn(entry[1], useKeys ? entry[0] : ii, seq) === false) { + return ii + 1; + } + } + return ii; + } + return seq.__iterateUncached(fn, reverse); + } + + function seqIterator(seq, type, reverse, useKeys) { + var cache = seq._cache; + if (cache) { + var maxIndex = cache.length - 1; + var ii = 0; + return new Iterator(function() { + var entry = cache[reverse ? maxIndex - ii : ii]; + return ii++ > maxIndex ? + iteratorDone() : + iteratorValue(type, useKeys ? entry[0] : ii - 1, entry[1]); + }); + } + return seq.__iteratorUncached(type, reverse); + } + + function fromJS(json, converter) { + return converter ? + fromJSWith(converter, json, '', {'': json}) : + fromJSDefault(json); + } + + function fromJSWith(converter, json, key, parentJSON) { + if (Array.isArray(json)) { + return converter.call(parentJSON, key, IndexedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)})); + } + if (isPlainObj(json)) { + return converter.call(parentJSON, key, KeyedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)})); + } + return json; + } + + function fromJSDefault(json) { + if (Array.isArray(json)) { + return IndexedSeq(json).map(fromJSDefault).toList(); + } + if (isPlainObj(json)) { + return KeyedSeq(json).map(fromJSDefault).toMap(); + } + return json; + } + + function isPlainObj(value) { + return value && (value.constructor === Object || value.constructor === undefined); + } + + /** + * An extension of the "same-value" algorithm as [described for use by ES6 Map + * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality) + * + * NaN is considered the same as NaN, however -0 and 0 are considered the same + * value, which is different from the algorithm described by + * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). + * + * This is extended further to allow Objects to describe the values they + * represent, by way of `valueOf` or `equals` (and `hashCode`). + * + * Note: because of this extension, the key equality of Immutable.Map and the + * value equality of Immutable.Set will differ from ES6 Map and Set. + * + * ### Defining custom values + * + * The easiest way to describe the value an object represents is by implementing + * `valueOf`. For example, `Date` represents a value by returning a unix + * timestamp for `valueOf`: + * + * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ... + * var date2 = new Date(1234567890000); + * date1.valueOf(); // 1234567890000 + * assert( date1 !== date2 ); + * assert( Immutable.is( date1, date2 ) ); + * + * Note: overriding `valueOf` may have other implications if you use this object + * where JavaScript expects a primitive, such as implicit string coercion. + * + * For more complex types, especially collections, implementing `valueOf` may + * not be performant. An alternative is to implement `equals` and `hashCode`. + * + * `equals` takes another object, presumably of similar type, and returns true + * if the it is equal. Equality is symmetrical, so the same result should be + * returned if this and the argument are flipped. + * + * assert( a.equals(b) === b.equals(a) ); + * + * `hashCode` returns a 32bit integer number representing the object which will + * be used to determine how to store the value object in a Map or Set. You must + * provide both or neither methods, one must not exist without the other. + * + * Also, an important relationship between these methods must be upheld: if two + * values are equal, they *must* return the same hashCode. If the values are not + * equal, they might have the same hashCode; this is called a hash collision, + * and while undesirable for performance reasons, it is acceptable. + * + * if (a.equals(b)) { + * assert( a.hashCode() === b.hashCode() ); + * } + * + * All Immutable collections implement `equals` and `hashCode`. + * + */ + function is(valueA, valueB) { + if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { + return true; + } + if (!valueA || !valueB) { + return false; + } + if (typeof valueA.valueOf === 'function' && + typeof valueB.valueOf === 'function') { + valueA = valueA.valueOf(); + valueB = valueB.valueOf(); + if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) { + return true; + } + if (!valueA || !valueB) { + return false; + } + } + if (typeof valueA.equals === 'function' && + typeof valueB.equals === 'function' && + valueA.equals(valueB)) { + return true; + } + return false; + } + + function deepEqual(a, b) { + if (a === b) { + return true; + } + + if ( + !isIterable(b) || + a.size !== undefined && b.size !== undefined && a.size !== b.size || + a.__hash !== undefined && b.__hash !== undefined && a.__hash !== b.__hash || + isKeyed(a) !== isKeyed(b) || + isIndexed(a) !== isIndexed(b) || + isOrdered(a) !== isOrdered(b) + ) { + return false; + } + + if (a.size === 0 && b.size === 0) { + return true; + } + + var notAssociative = !isAssociative(a); + + if (isOrdered(a)) { + var entries = a.entries(); + return b.every(function(v, k) { + var entry = entries.next().value; + return entry && is(entry[1], v) && (notAssociative || is(entry[0], k)); + }) && entries.next().done; + } + + var flipped = false; + + if (a.size === undefined) { + if (b.size === undefined) { + if (typeof a.cacheResult === 'function') { + a.cacheResult(); + } + } else { + flipped = true; + var _ = a; + a = b; + b = _; + } + } + + var allEqual = true; + var bSize = b.__iterate(function(v, k) { + if (notAssociative ? !a.has(v) : + flipped ? !is(v, a.get(k, NOT_SET)) : !is(a.get(k, NOT_SET), v)) { + allEqual = false; + return false; + } + }); + + return allEqual && a.size === bSize; + } + + createClass(Repeat, IndexedSeq); + + function Repeat(value, times) { + if (!(this instanceof Repeat)) { + return new Repeat(value, times); + } + this._value = value; + this.size = times === undefined ? Infinity : Math.max(0, times); + if (this.size === 0) { + if (EMPTY_REPEAT) { + return EMPTY_REPEAT; + } + EMPTY_REPEAT = this; + } + } + + Repeat.prototype.toString = function() { + if (this.size === 0) { + return 'Repeat []'; + } + return 'Repeat [ ' + this._value + ' ' + this.size + ' times ]'; + }; + + Repeat.prototype.get = function(index, notSetValue) { + return this.has(index) ? this._value : notSetValue; + }; + + Repeat.prototype.includes = function(searchValue) { + return is(this._value, searchValue); + }; + + Repeat.prototype.slice = function(begin, end) { + var size = this.size; + return wholeSlice(begin, end, size) ? this : + new Repeat(this._value, resolveEnd(end, size) - resolveBegin(begin, size)); + }; + + Repeat.prototype.reverse = function() { + return this; + }; + + Repeat.prototype.indexOf = function(searchValue) { + if (is(this._value, searchValue)) { + return 0; + } + return -1; + }; + + Repeat.prototype.lastIndexOf = function(searchValue) { + if (is(this._value, searchValue)) { + return this.size; + } + return -1; + }; + + Repeat.prototype.__iterate = function(fn, reverse) { + for (var ii = 0; ii < this.size; ii++) { + if (fn(this._value, ii, this) === false) { + return ii + 1; + } + } + return ii; + }; + + Repeat.prototype.__iterator = function(type, reverse) {var this$0 = this; + var ii = 0; + return new Iterator(function() + {return ii < this$0.size ? iteratorValue(type, ii++, this$0._value) : iteratorDone()} + ); + }; + + Repeat.prototype.equals = function(other) { + return other instanceof Repeat ? + is(this._value, other._value) : + deepEqual(other); + }; + + + var EMPTY_REPEAT; + + function invariant(condition, error) { + if (!condition) throw new Error(error); + } + + createClass(Range, IndexedSeq); + + function Range(start, end, step) { + if (!(this instanceof Range)) { + return new Range(start, end, step); + } + invariant(step !== 0, 'Cannot step a Range by 0'); + start = start || 0; + if (end === undefined) { + end = Infinity; + } + step = step === undefined ? 1 : Math.abs(step); + if (end < start) { + step = -step; + } + this._start = start; + this._end = end; + this._step = step; + this.size = Math.max(0, Math.ceil((end - start) / step - 1) + 1); + if (this.size === 0) { + if (EMPTY_RANGE) { + return EMPTY_RANGE; + } + EMPTY_RANGE = this; + } + } + + Range.prototype.toString = function() { + if (this.size === 0) { + return 'Range []'; + } + return 'Range [ ' + + this._start + '...' + this._end + + (this._step !== 1 ? ' by ' + this._step : '') + + ' ]'; + }; + + Range.prototype.get = function(index, notSetValue) { + return this.has(index) ? + this._start + wrapIndex(this, index) * this._step : + notSetValue; + }; + + Range.prototype.includes = function(searchValue) { + var possibleIndex = (searchValue - this._start) / this._step; + return possibleIndex >= 0 && + possibleIndex < this.size && + possibleIndex === Math.floor(possibleIndex); + }; + + Range.prototype.slice = function(begin, end) { + if (wholeSlice(begin, end, this.size)) { + return this; + } + begin = resolveBegin(begin, this.size); + end = resolveEnd(end, this.size); + if (end <= begin) { + return new Range(0, 0); + } + return new Range(this.get(begin, this._end), this.get(end, this._end), this._step); + }; + + Range.prototype.indexOf = function(searchValue) { + var offsetValue = searchValue - this._start; + if (offsetValue % this._step === 0) { + var index = offsetValue / this._step; + if (index >= 0 && index < this.size) { + return index + } + } + return -1; + }; + + Range.prototype.lastIndexOf = function(searchValue) { + return this.indexOf(searchValue); + }; + + Range.prototype.__iterate = function(fn, reverse) { + var maxIndex = this.size - 1; + var step = this._step; + var value = reverse ? this._start + maxIndex * step : this._start; + for (var ii = 0; ii <= maxIndex; ii++) { + if (fn(value, ii, this) === false) { + return ii + 1; + } + value += reverse ? -step : step; + } + return ii; + }; + + Range.prototype.__iterator = function(type, reverse) { + var maxIndex = this.size - 1; + var step = this._step; + var value = reverse ? this._start + maxIndex * step : this._start; + var ii = 0; + return new Iterator(function() { + var v = value; + value += reverse ? -step : step; + return ii > maxIndex ? iteratorDone() : iteratorValue(type, ii++, v); + }); + }; + + Range.prototype.equals = function(other) { + return other instanceof Range ? + this._start === other._start && + this._end === other._end && + this._step === other._step : + deepEqual(this, other); + }; + + + var EMPTY_RANGE; + + createClass(Collection, Iterable); + function Collection() { + throw TypeError('Abstract'); + } + + + createClass(KeyedCollection, Collection);function KeyedCollection() {} + + createClass(IndexedCollection, Collection);function IndexedCollection() {} + + createClass(SetCollection, Collection);function SetCollection() {} + + + Collection.Keyed = KeyedCollection; + Collection.Indexed = IndexedCollection; + Collection.Set = SetCollection; + + var imul = + typeof Math.imul === 'function' && Math.imul(0xffffffff, 2) === -2 ? + Math.imul : + function imul(a, b) { + a = a | 0; // int + b = b | 0; // int + var c = a & 0xffff; + var d = b & 0xffff; + // Shift by 0 fixes the sign on the high part. + return (c * d) + ((((a >>> 16) * d + c * (b >>> 16)) << 16) >>> 0) | 0; // int + }; + + // v8 has an optimization for storing 31-bit signed numbers. + // Values which have either 00 or 11 as the high order bits qualify. + // This function drops the highest order bit in a signed number, maintaining + // the sign bit. + function smi(i32) { + return ((i32 >>> 1) & 0x40000000) | (i32 & 0xBFFFFFFF); + } + + function hash(o) { + if (o === false || o === null || o === undefined) { + return 0; + } + if (typeof o.valueOf === 'function') { + o = o.valueOf(); + if (o === false || o === null || o === undefined) { + return 0; + } + } + if (o === true) { + return 1; + } + var type = typeof o; + if (type === 'number') { + if (o !== o || o === Infinity) { + return 0; + } + var h = o | 0; + if (h !== o) { + h ^= o * 0xFFFFFFFF; + } + while (o > 0xFFFFFFFF) { + o /= 0xFFFFFFFF; + h ^= o; + } + return smi(h); + } + if (type === 'string') { + return o.length > STRING_HASH_CACHE_MIN_STRLEN ? cachedHashString(o) : hashString(o); + } + if (typeof o.hashCode === 'function') { + return o.hashCode(); + } + if (type === 'object') { + return hashJSObj(o); + } + if (typeof o.toString === 'function') { + return hashString(o.toString()); + } + throw new Error('Value type ' + type + ' cannot be hashed.'); + } + + function cachedHashString(string) { + var hash = stringHashCache[string]; + if (hash === undefined) { + hash = hashString(string); + if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) { + STRING_HASH_CACHE_SIZE = 0; + stringHashCache = {}; + } + STRING_HASH_CACHE_SIZE++; + stringHashCache[string] = hash; + } + return hash; + } + + // http://jsperf.com/hashing-strings + function hashString(string) { + // This is the hash from JVM + // The hash code for a string is computed as + // s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], + // where s[i] is the ith character of the string and n is the length of + // the string. We "mod" the result to make it between 0 (inclusive) and 2^31 + // (exclusive) by dropping high bits. + var hash = 0; + for (var ii = 0; ii < string.length; ii++) { + hash = 31 * hash + string.charCodeAt(ii) | 0; + } + return smi(hash); + } + + function hashJSObj(obj) { + var hash; + if (usingWeakMap) { + hash = weakMap.get(obj); + if (hash !== undefined) { + return hash; + } + } + + hash = obj[UID_HASH_KEY]; + if (hash !== undefined) { + return hash; + } + + if (!canDefineProperty) { + hash = obj.propertyIsEnumerable && obj.propertyIsEnumerable[UID_HASH_KEY]; + if (hash !== undefined) { + return hash; + } + + hash = getIENodeHash(obj); + if (hash !== undefined) { + return hash; + } + } + + hash = ++objHashUID; + if (objHashUID & 0x40000000) { + objHashUID = 0; + } + + if (usingWeakMap) { + weakMap.set(obj, hash); + } else if (isExtensible !== undefined && isExtensible(obj) === false) { + throw new Error('Non-extensible objects are not allowed as keys.'); + } else if (canDefineProperty) { + Object.defineProperty(obj, UID_HASH_KEY, { + 'enumerable': false, + 'configurable': false, + 'writable': false, + 'value': hash + }); + } else if (obj.propertyIsEnumerable !== undefined && + obj.propertyIsEnumerable === obj.constructor.prototype.propertyIsEnumerable) { + // Since we can't define a non-enumerable property on the object + // we'll hijack one of the less-used non-enumerable properties to + // save our hash on it. Since this is a function it will not show up in + // `JSON.stringify` which is what we want. + obj.propertyIsEnumerable = function() { + return this.constructor.prototype.propertyIsEnumerable.apply(this, arguments); + }; + obj.propertyIsEnumerable[UID_HASH_KEY] = hash; + } else if (obj.nodeType !== undefined) { + // At this point we couldn't get the IE `uniqueID` to use as a hash + // and we couldn't use a non-enumerable property to exploit the + // dontEnum bug so we simply add the `UID_HASH_KEY` on the node + // itself. + obj[UID_HASH_KEY] = hash; + } else { + throw new Error('Unable to set a non-enumerable property on object.'); + } + + return hash; + } + + // Get references to ES5 object methods. + var isExtensible = Object.isExtensible; + + // True if Object.defineProperty works as expected. IE8 fails this test. + var canDefineProperty = (function() { + try { + Object.defineProperty({}, '@', {}); + return true; + } catch (e) { + return false; + } + }()); + + // IE has a `uniqueID` property on DOM nodes. We can construct the hash from it + // and avoid memory leaks from the IE cloneNode bug. + function getIENodeHash(node) { + if (node && node.nodeType > 0) { + switch (node.nodeType) { + case 1: // Element + return node.uniqueID; + case 9: // Document + return node.documentElement && node.documentElement.uniqueID; + } + } + } + + // If possible, use a WeakMap. + var usingWeakMap = typeof WeakMap === 'function'; + var weakMap; + if (usingWeakMap) { + weakMap = new WeakMap(); + } + + var objHashUID = 0; + + var UID_HASH_KEY = '__immutablehash__'; + if (typeof Symbol === 'function') { + UID_HASH_KEY = Symbol(UID_HASH_KEY); + } + + var STRING_HASH_CACHE_MIN_STRLEN = 16; + var STRING_HASH_CACHE_MAX_SIZE = 255; + var STRING_HASH_CACHE_SIZE = 0; + var stringHashCache = {}; + + function assertNotInfinite(size) { + invariant( + size !== Infinity, + 'Cannot perform this action with an infinite size.' + ); + } + + createClass(Map, KeyedCollection); + + // @pragma Construction + + function Map(value) { + return value === null || value === undefined ? emptyMap() : + isMap(value) && !isOrdered(value) ? value : + emptyMap().withMutations(function(map ) { + var iter = KeyedIterable(value); + assertNotInfinite(iter.size); + iter.forEach(function(v, k) {return map.set(k, v)}); + }); + } + + Map.of = function() {var keyValues = SLICE$0.call(arguments, 0); + return emptyMap().withMutations(function(map ) { + for (var i = 0; i < keyValues.length; i += 2) { + if (i + 1 >= keyValues.length) { + throw new Error('Missing value for key: ' + keyValues[i]); + } + map.set(keyValues[i], keyValues[i + 1]); + } + }); + }; + + Map.prototype.toString = function() { + return this.__toString('Map {', '}'); + }; + + // @pragma Access + + Map.prototype.get = function(k, notSetValue) { + return this._root ? + this._root.get(0, undefined, k, notSetValue) : + notSetValue; + }; + + // @pragma Modification + + Map.prototype.set = function(k, v) { + return updateMap(this, k, v); + }; + + Map.prototype.setIn = function(keyPath, v) { + return this.updateIn(keyPath, NOT_SET, function() {return v}); + }; + + Map.prototype.remove = function(k) { + return updateMap(this, k, NOT_SET); + }; + + Map.prototype.deleteIn = function(keyPath) { + return this.updateIn(keyPath, function() {return NOT_SET}); + }; + + Map.prototype.update = function(k, notSetValue, updater) { + return arguments.length === 1 ? + k(this) : + this.updateIn([k], notSetValue, updater); + }; + + Map.prototype.updateIn = function(keyPath, notSetValue, updater) { + if (!updater) { + updater = notSetValue; + notSetValue = undefined; + } + var updatedValue = updateInDeepMap( + this, + forceIterator(keyPath), + notSetValue, + updater + ); + return updatedValue === NOT_SET ? undefined : updatedValue; + }; + + Map.prototype.clear = function() { + if (this.size === 0) { + return this; + } + if (this.__ownerID) { + this.size = 0; + this._root = null; + this.__hash = undefined; + this.__altered = true; + return this; + } + return emptyMap(); + }; + + // @pragma Composition + + Map.prototype.merge = function(/*...iters*/) { + return mergeIntoMapWith(this, undefined, arguments); + }; + + Map.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1); + return mergeIntoMapWith(this, merger, iters); + }; + + Map.prototype.mergeIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1); + return this.updateIn( + keyPath, + emptyMap(), + function(m ) {return typeof m.merge === 'function' ? + m.merge.apply(m, iters) : + iters[iters.length - 1]} + ); + }; + + Map.prototype.mergeDeep = function(/*...iters*/) { + return mergeIntoMapWith(this, deepMerger, arguments); + }; + + Map.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1); + return mergeIntoMapWith(this, deepMergerWith(merger), iters); + }; + + Map.prototype.mergeDeepIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1); + return this.updateIn( + keyPath, + emptyMap(), + function(m ) {return typeof m.mergeDeep === 'function' ? + m.mergeDeep.apply(m, iters) : + iters[iters.length - 1]} + ); + }; + + Map.prototype.sort = function(comparator) { + // Late binding + return OrderedMap(sortFactory(this, comparator)); + }; + + Map.prototype.sortBy = function(mapper, comparator) { + // Late binding + return OrderedMap(sortFactory(this, comparator, mapper)); + }; + + // @pragma Mutability + + Map.prototype.withMutations = function(fn) { + var mutable = this.asMutable(); + fn(mutable); + return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this; + }; + + Map.prototype.asMutable = function() { + return this.__ownerID ? this : this.__ensureOwner(new OwnerID()); + }; + + Map.prototype.asImmutable = function() { + return this.__ensureOwner(); + }; + + Map.prototype.wasAltered = function() { + return this.__altered; + }; + + Map.prototype.__iterator = function(type, reverse) { + return new MapIterator(this, type, reverse); + }; + + Map.prototype.__iterate = function(fn, reverse) {var this$0 = this; + var iterations = 0; + this._root && this._root.iterate(function(entry ) { + iterations++; + return fn(entry[1], entry[0], this$0); + }, reverse); + return iterations; + }; + + Map.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + if (!ownerID) { + this.__ownerID = ownerID; + this.__altered = false; + return this; + } + return makeMap(this.size, this._root, ownerID, this.__hash); + }; + + + function isMap(maybeMap) { + return !!(maybeMap && maybeMap[IS_MAP_SENTINEL]); + } + + Map.isMap = isMap; + + var IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@'; + + var MapPrototype = Map.prototype; + MapPrototype[IS_MAP_SENTINEL] = true; + MapPrototype[DELETE] = MapPrototype.remove; + MapPrototype.removeIn = MapPrototype.deleteIn; + + + // #pragma Trie Nodes + + + + function ArrayMapNode(ownerID, entries) { + this.ownerID = ownerID; + this.entries = entries; + } + + ArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) { + var entries = this.entries; + for (var ii = 0, len = entries.length; ii < len; ii++) { + if (is(key, entries[ii][0])) { + return entries[ii][1]; + } + } + return notSetValue; + }; + + ArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + var removed = value === NOT_SET; + + var entries = this.entries; + var idx = 0; + for (var len = entries.length; idx < len; idx++) { + if (is(key, entries[idx][0])) { + break; + } + } + var exists = idx < len; + + if (exists ? entries[idx][1] === value : removed) { + return this; + } + + SetRef(didAlter); + (removed || !exists) && SetRef(didChangeSize); + + if (removed && entries.length === 1) { + return; // undefined + } + + if (!exists && !removed && entries.length >= MAX_ARRAY_MAP_SIZE) { + return createNodes(ownerID, entries, key, value); + } + + var isEditable = ownerID && ownerID === this.ownerID; + var newEntries = isEditable ? entries : arrCopy(entries); + + if (exists) { + if (removed) { + idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop()); + } else { + newEntries[idx] = [key, value]; + } + } else { + newEntries.push([key, value]); + } + + if (isEditable) { + this.entries = newEntries; + return this; + } + + return new ArrayMapNode(ownerID, newEntries); + }; + + + + + function BitmapIndexedNode(ownerID, bitmap, nodes) { + this.ownerID = ownerID; + this.bitmap = bitmap; + this.nodes = nodes; + } + + BitmapIndexedNode.prototype.get = function(shift, keyHash, key, notSetValue) { + if (keyHash === undefined) { + keyHash = hash(key); + } + var bit = (1 << ((shift === 0 ? keyHash : keyHash >>> shift) & MASK)); + var bitmap = this.bitmap; + return (bitmap & bit) === 0 ? notSetValue : + this.nodes[popCount(bitmap & (bit - 1))].get(shift + SHIFT, keyHash, key, notSetValue); + }; + + BitmapIndexedNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + if (keyHash === undefined) { + keyHash = hash(key); + } + var keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + var bit = 1 << keyHashFrag; + var bitmap = this.bitmap; + var exists = (bitmap & bit) !== 0; + + if (!exists && value === NOT_SET) { + return this; + } + + var idx = popCount(bitmap & (bit - 1)); + var nodes = this.nodes; + var node = exists ? nodes[idx] : undefined; + var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter); + + if (newNode === node) { + return this; + } + + if (!exists && newNode && nodes.length >= MAX_BITMAP_INDEXED_SIZE) { + return expandNodes(ownerID, nodes, bitmap, keyHashFrag, newNode); + } + + if (exists && !newNode && nodes.length === 2 && isLeafNode(nodes[idx ^ 1])) { + return nodes[idx ^ 1]; + } + + if (exists && newNode && nodes.length === 1 && isLeafNode(newNode)) { + return newNode; + } + + var isEditable = ownerID && ownerID === this.ownerID; + var newBitmap = exists ? newNode ? bitmap : bitmap ^ bit : bitmap | bit; + var newNodes = exists ? newNode ? + setIn(nodes, idx, newNode, isEditable) : + spliceOut(nodes, idx, isEditable) : + spliceIn(nodes, idx, newNode, isEditable); + + if (isEditable) { + this.bitmap = newBitmap; + this.nodes = newNodes; + return this; + } + + return new BitmapIndexedNode(ownerID, newBitmap, newNodes); + }; + + + + + function HashArrayMapNode(ownerID, count, nodes) { + this.ownerID = ownerID; + this.count = count; + this.nodes = nodes; + } + + HashArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) { + if (keyHash === undefined) { + keyHash = hash(key); + } + var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + var node = this.nodes[idx]; + return node ? node.get(shift + SHIFT, keyHash, key, notSetValue) : notSetValue; + }; + + HashArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + if (keyHash === undefined) { + keyHash = hash(key); + } + var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + var removed = value === NOT_SET; + var nodes = this.nodes; + var node = nodes[idx]; + + if (removed && !node) { + return this; + } + + var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter); + if (newNode === node) { + return this; + } + + var newCount = this.count; + if (!node) { + newCount++; + } else if (!newNode) { + newCount--; + if (newCount < MIN_HASH_ARRAY_MAP_SIZE) { + return packNodes(ownerID, nodes, newCount, idx); + } + } + + var isEditable = ownerID && ownerID === this.ownerID; + var newNodes = setIn(nodes, idx, newNode, isEditable); + + if (isEditable) { + this.count = newCount; + this.nodes = newNodes; + return this; + } + + return new HashArrayMapNode(ownerID, newCount, newNodes); + }; + + + + + function HashCollisionNode(ownerID, keyHash, entries) { + this.ownerID = ownerID; + this.keyHash = keyHash; + this.entries = entries; + } + + HashCollisionNode.prototype.get = function(shift, keyHash, key, notSetValue) { + var entries = this.entries; + for (var ii = 0, len = entries.length; ii < len; ii++) { + if (is(key, entries[ii][0])) { + return entries[ii][1]; + } + } + return notSetValue; + }; + + HashCollisionNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + if (keyHash === undefined) { + keyHash = hash(key); + } + + var removed = value === NOT_SET; + + if (keyHash !== this.keyHash) { + if (removed) { + return this; + } + SetRef(didAlter); + SetRef(didChangeSize); + return mergeIntoNode(this, ownerID, shift, keyHash, [key, value]); + } + + var entries = this.entries; + var idx = 0; + for (var len = entries.length; idx < len; idx++) { + if (is(key, entries[idx][0])) { + break; + } + } + var exists = idx < len; + + if (exists ? entries[idx][1] === value : removed) { + return this; + } + + SetRef(didAlter); + (removed || !exists) && SetRef(didChangeSize); + + if (removed && len === 2) { + return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]); + } + + var isEditable = ownerID && ownerID === this.ownerID; + var newEntries = isEditable ? entries : arrCopy(entries); + + if (exists) { + if (removed) { + idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop()); + } else { + newEntries[idx] = [key, value]; + } + } else { + newEntries.push([key, value]); + } + + if (isEditable) { + this.entries = newEntries; + return this; + } + + return new HashCollisionNode(ownerID, this.keyHash, newEntries); + }; + + + + + function ValueNode(ownerID, keyHash, entry) { + this.ownerID = ownerID; + this.keyHash = keyHash; + this.entry = entry; + } + + ValueNode.prototype.get = function(shift, keyHash, key, notSetValue) { + return is(key, this.entry[0]) ? this.entry[1] : notSetValue; + }; + + ValueNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + var removed = value === NOT_SET; + var keyMatch = is(key, this.entry[0]); + if (keyMatch ? value === this.entry[1] : removed) { + return this; + } + + SetRef(didAlter); + + if (removed) { + SetRef(didChangeSize); + return; // undefined + } + + if (keyMatch) { + if (ownerID && ownerID === this.ownerID) { + this.entry[1] = value; + return this; + } + return new ValueNode(ownerID, this.keyHash, [key, value]); + } + + SetRef(didChangeSize); + return mergeIntoNode(this, ownerID, shift, hash(key), [key, value]); + }; + + + + // #pragma Iterators + + ArrayMapNode.prototype.iterate = + HashCollisionNode.prototype.iterate = function (fn, reverse) { + var entries = this.entries; + for (var ii = 0, maxIndex = entries.length - 1; ii <= maxIndex; ii++) { + if (fn(entries[reverse ? maxIndex - ii : ii]) === false) { + return false; + } + } + } + + BitmapIndexedNode.prototype.iterate = + HashArrayMapNode.prototype.iterate = function (fn, reverse) { + var nodes = this.nodes; + for (var ii = 0, maxIndex = nodes.length - 1; ii <= maxIndex; ii++) { + var node = nodes[reverse ? maxIndex - ii : ii]; + if (node && node.iterate(fn, reverse) === false) { + return false; + } + } + } + + ValueNode.prototype.iterate = function (fn, reverse) { + return fn(this.entry); + } + + createClass(MapIterator, Iterator); + + function MapIterator(map, type, reverse) { + this._type = type; + this._reverse = reverse; + this._stack = map._root && mapIteratorFrame(map._root); + } + + MapIterator.prototype.next = function() { + var type = this._type; + var stack = this._stack; + while (stack) { + var node = stack.node; + var index = stack.index++; + var maxIndex; + if (node.entry) { + if (index === 0) { + return mapIteratorValue(type, node.entry); + } + } else if (node.entries) { + maxIndex = node.entries.length - 1; + if (index <= maxIndex) { + return mapIteratorValue(type, node.entries[this._reverse ? maxIndex - index : index]); + } + } else { + maxIndex = node.nodes.length - 1; + if (index <= maxIndex) { + var subNode = node.nodes[this._reverse ? maxIndex - index : index]; + if (subNode) { + if (subNode.entry) { + return mapIteratorValue(type, subNode.entry); + } + stack = this._stack = mapIteratorFrame(subNode, stack); + } + continue; + } + } + stack = this._stack = this._stack.__prev; + } + return iteratorDone(); + }; + + + function mapIteratorValue(type, entry) { + return iteratorValue(type, entry[0], entry[1]); + } + + function mapIteratorFrame(node, prev) { + return { + node: node, + index: 0, + __prev: prev + }; + } + + function makeMap(size, root, ownerID, hash) { + var map = Object.create(MapPrototype); + map.size = size; + map._root = root; + map.__ownerID = ownerID; + map.__hash = hash; + map.__altered = false; + return map; + } + + var EMPTY_MAP; + function emptyMap() { + return EMPTY_MAP || (EMPTY_MAP = makeMap(0)); + } + + function updateMap(map, k, v) { + var newRoot; + var newSize; + if (!map._root) { + if (v === NOT_SET) { + return map; + } + newSize = 1; + newRoot = new ArrayMapNode(map.__ownerID, [[k, v]]); + } else { + var didChangeSize = MakeRef(CHANGE_LENGTH); + var didAlter = MakeRef(DID_ALTER); + newRoot = updateNode(map._root, map.__ownerID, 0, undefined, k, v, didChangeSize, didAlter); + if (!didAlter.value) { + return map; + } + newSize = map.size + (didChangeSize.value ? v === NOT_SET ? -1 : 1 : 0); + } + if (map.__ownerID) { + map.size = newSize; + map._root = newRoot; + map.__hash = undefined; + map.__altered = true; + return map; + } + return newRoot ? makeMap(newSize, newRoot) : emptyMap(); + } + + function updateNode(node, ownerID, shift, keyHash, key, value, didChangeSize, didAlter) { + if (!node) { + if (value === NOT_SET) { + return node; + } + SetRef(didAlter); + SetRef(didChangeSize); + return new ValueNode(ownerID, keyHash, [key, value]); + } + return node.update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter); + } + + function isLeafNode(node) { + return node.constructor === ValueNode || node.constructor === HashCollisionNode; + } + + function mergeIntoNode(node, ownerID, shift, keyHash, entry) { + if (node.keyHash === keyHash) { + return new HashCollisionNode(ownerID, keyHash, [node.entry, entry]); + } + + var idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK; + var idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; + + var newNode; + var nodes = idx1 === idx2 ? + [mergeIntoNode(node, ownerID, shift + SHIFT, keyHash, entry)] : + ((newNode = new ValueNode(ownerID, keyHash, entry)), idx1 < idx2 ? [node, newNode] : [newNode, node]); + + return new BitmapIndexedNode(ownerID, (1 << idx1) | (1 << idx2), nodes); + } + + function createNodes(ownerID, entries, key, value) { + if (!ownerID) { + ownerID = new OwnerID(); + } + var node = new ValueNode(ownerID, hash(key), [key, value]); + for (var ii = 0; ii < entries.length; ii++) { + var entry = entries[ii]; + node = node.update(ownerID, 0, undefined, entry[0], entry[1]); + } + return node; + } + + function packNodes(ownerID, nodes, count, excluding) { + var bitmap = 0; + var packedII = 0; + var packedNodes = new Array(count); + for (var ii = 0, bit = 1, len = nodes.length; ii < len; ii++, bit <<= 1) { + var node = nodes[ii]; + if (node !== undefined && ii !== excluding) { + bitmap |= bit; + packedNodes[packedII++] = node; + } + } + return new BitmapIndexedNode(ownerID, bitmap, packedNodes); + } + + function expandNodes(ownerID, nodes, bitmap, including, node) { + var count = 0; + var expandedNodes = new Array(SIZE); + for (var ii = 0; bitmap !== 0; ii++, bitmap >>>= 1) { + expandedNodes[ii] = bitmap & 1 ? nodes[count++] : undefined; + } + expandedNodes[including] = node; + return new HashArrayMapNode(ownerID, count + 1, expandedNodes); + } + + function mergeIntoMapWith(map, merger, iterables) { + var iters = []; + for (var ii = 0; ii < iterables.length; ii++) { + var value = iterables[ii]; + var iter = KeyedIterable(value); + if (!isIterable(value)) { + iter = iter.map(function(v ) {return fromJS(v)}); + } + iters.push(iter); + } + return mergeIntoCollectionWith(map, merger, iters); + } + + function deepMerger(existing, value, key) { + return existing && existing.mergeDeep && isIterable(value) ? + existing.mergeDeep(value) : + is(existing, value) ? existing : value; + } + + function deepMergerWith(merger) { + return function(existing, value, key) { + if (existing && existing.mergeDeepWith && isIterable(value)) { + return existing.mergeDeepWith(merger, value); + } + var nextValue = merger(existing, value, key); + return is(existing, nextValue) ? existing : nextValue; + }; + } + + function mergeIntoCollectionWith(collection, merger, iters) { + iters = iters.filter(function(x ) {return x.size !== 0}); + if (iters.length === 0) { + return collection; + } + if (collection.size === 0 && !collection.__ownerID && iters.length === 1) { + return collection.constructor(iters[0]); + } + return collection.withMutations(function(collection ) { + var mergeIntoMap = merger ? + function(value, key) { + collection.update(key, NOT_SET, function(existing ) + {return existing === NOT_SET ? value : merger(existing, value, key)} + ); + } : + function(value, key) { + collection.set(key, value); + } + for (var ii = 0; ii < iters.length; ii++) { + iters[ii].forEach(mergeIntoMap); + } + }); + } + + function updateInDeepMap(existing, keyPathIter, notSetValue, updater) { + var isNotSet = existing === NOT_SET; + var step = keyPathIter.next(); + if (step.done) { + var existingValue = isNotSet ? notSetValue : existing; + var newValue = updater(existingValue); + return newValue === existingValue ? existing : newValue; + } + invariant( + isNotSet || (existing && existing.set), + 'invalid keyPath' + ); + var key = step.value; + var nextExisting = isNotSet ? NOT_SET : existing.get(key, NOT_SET); + var nextUpdated = updateInDeepMap( + nextExisting, + keyPathIter, + notSetValue, + updater + ); + return nextUpdated === nextExisting ? existing : + nextUpdated === NOT_SET ? existing.remove(key) : + (isNotSet ? emptyMap() : existing).set(key, nextUpdated); + } + + function popCount(x) { + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0f0f0f0f; + x = x + (x >> 8); + x = x + (x >> 16); + return x & 0x7f; + } + + function setIn(array, idx, val, canEdit) { + var newArray = canEdit ? array : arrCopy(array); + newArray[idx] = val; + return newArray; + } + + function spliceIn(array, idx, val, canEdit) { + var newLen = array.length + 1; + if (canEdit && idx + 1 === newLen) { + array[idx] = val; + return array; + } + var newArray = new Array(newLen); + var after = 0; + for (var ii = 0; ii < newLen; ii++) { + if (ii === idx) { + newArray[ii] = val; + after = -1; + } else { + newArray[ii] = array[ii + after]; + } + } + return newArray; + } + + function spliceOut(array, idx, canEdit) { + var newLen = array.length - 1; + if (canEdit && idx === newLen) { + array.pop(); + return array; + } + var newArray = new Array(newLen); + var after = 0; + for (var ii = 0; ii < newLen; ii++) { + if (ii === idx) { + after = 1; + } + newArray[ii] = array[ii + after]; + } + return newArray; + } + + var MAX_ARRAY_MAP_SIZE = SIZE / 4; + var MAX_BITMAP_INDEXED_SIZE = SIZE / 2; + var MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4; + + createClass(List, IndexedCollection); + + // @pragma Construction + + function List(value) { + var empty = emptyList(); + if (value === null || value === undefined) { + return empty; + } + if (isList(value)) { + return value; + } + var iter = IndexedIterable(value); + var size = iter.size; + if (size === 0) { + return empty; + } + assertNotInfinite(size); + if (size > 0 && size < SIZE) { + return makeList(0, size, SHIFT, null, new VNode(iter.toArray())); + } + return empty.withMutations(function(list ) { + list.setSize(size); + iter.forEach(function(v, i) {return list.set(i, v)}); + }); + } + + List.of = function(/*...values*/) { + return this(arguments); + }; + + List.prototype.toString = function() { + return this.__toString('List [', ']'); + }; + + // @pragma Access + + List.prototype.get = function(index, notSetValue) { + index = wrapIndex(this, index); + if (index >= 0 && index < this.size) { + index += this._origin; + var node = listNodeFor(this, index); + return node && node.array[index & MASK]; + } + return notSetValue; + }; + + // @pragma Modification + + List.prototype.set = function(index, value) { + return updateList(this, index, value); + }; + + List.prototype.remove = function(index) { + return !this.has(index) ? this : + index === 0 ? this.shift() : + index === this.size - 1 ? this.pop() : + this.splice(index, 1); + }; + + List.prototype.insert = function(index, value) { + return this.splice(index, 0, value); + }; + + List.prototype.clear = function() { + if (this.size === 0) { + return this; + } + if (this.__ownerID) { + this.size = this._origin = this._capacity = 0; + this._level = SHIFT; + this._root = this._tail = null; + this.__hash = undefined; + this.__altered = true; + return this; + } + return emptyList(); + }; + + List.prototype.push = function(/*...values*/) { + var values = arguments; + var oldSize = this.size; + return this.withMutations(function(list ) { + setListBounds(list, 0, oldSize + values.length); + for (var ii = 0; ii < values.length; ii++) { + list.set(oldSize + ii, values[ii]); + } + }); + }; + + List.prototype.pop = function() { + return setListBounds(this, 0, -1); + }; + + List.prototype.unshift = function(/*...values*/) { + var values = arguments; + return this.withMutations(function(list ) { + setListBounds(list, -values.length); + for (var ii = 0; ii < values.length; ii++) { + list.set(ii, values[ii]); + } + }); + }; + + List.prototype.shift = function() { + return setListBounds(this, 1); + }; + + // @pragma Composition + + List.prototype.merge = function(/*...iters*/) { + return mergeIntoListWith(this, undefined, arguments); + }; + + List.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1); + return mergeIntoListWith(this, merger, iters); + }; + + List.prototype.mergeDeep = function(/*...iters*/) { + return mergeIntoListWith(this, deepMerger, arguments); + }; + + List.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1); + return mergeIntoListWith(this, deepMergerWith(merger), iters); + }; + + List.prototype.setSize = function(size) { + return setListBounds(this, 0, size); + }; + + // @pragma Iteration + + List.prototype.slice = function(begin, end) { + var size = this.size; + if (wholeSlice(begin, end, size)) { + return this; + } + return setListBounds( + this, + resolveBegin(begin, size), + resolveEnd(end, size) + ); + }; + + List.prototype.__iterator = function(type, reverse) { + var index = 0; + var values = iterateList(this, reverse); + return new Iterator(function() { + var value = values(); + return value === DONE ? + iteratorDone() : + iteratorValue(type, index++, value); + }); + }; + + List.prototype.__iterate = function(fn, reverse) { + var index = 0; + var values = iterateList(this, reverse); + var value; + while ((value = values()) !== DONE) { + if (fn(value, index++, this) === false) { + break; + } + } + return index; + }; + + List.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + if (!ownerID) { + this.__ownerID = ownerID; + return this; + } + return makeList(this._origin, this._capacity, this._level, this._root, this._tail, ownerID, this.__hash); + }; + + + function isList(maybeList) { + return !!(maybeList && maybeList[IS_LIST_SENTINEL]); + } + + List.isList = isList; + + var IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@'; + + var ListPrototype = List.prototype; + ListPrototype[IS_LIST_SENTINEL] = true; + ListPrototype[DELETE] = ListPrototype.remove; + ListPrototype.setIn = MapPrototype.setIn; + ListPrototype.deleteIn = + ListPrototype.removeIn = MapPrototype.removeIn; + ListPrototype.update = MapPrototype.update; + ListPrototype.updateIn = MapPrototype.updateIn; + ListPrototype.mergeIn = MapPrototype.mergeIn; + ListPrototype.mergeDeepIn = MapPrototype.mergeDeepIn; + ListPrototype.withMutations = MapPrototype.withMutations; + ListPrototype.asMutable = MapPrototype.asMutable; + ListPrototype.asImmutable = MapPrototype.asImmutable; + ListPrototype.wasAltered = MapPrototype.wasAltered; + + + + function VNode(array, ownerID) { + this.array = array; + this.ownerID = ownerID; + } + + // TODO: seems like these methods are very similar + + VNode.prototype.removeBefore = function(ownerID, level, index) { + if (index === level ? 1 << level : 0 || this.array.length === 0) { + return this; + } + var originIndex = (index >>> level) & MASK; + if (originIndex >= this.array.length) { + return new VNode([], ownerID); + } + var removingFirst = originIndex === 0; + var newChild; + if (level > 0) { + var oldChild = this.array[originIndex]; + newChild = oldChild && oldChild.removeBefore(ownerID, level - SHIFT, index); + if (newChild === oldChild && removingFirst) { + return this; + } + } + if (removingFirst && !newChild) { + return this; + } + var editable = editableVNode(this, ownerID); + if (!removingFirst) { + for (var ii = 0; ii < originIndex; ii++) { + editable.array[ii] = undefined; + } + } + if (newChild) { + editable.array[originIndex] = newChild; + } + return editable; + }; + + VNode.prototype.removeAfter = function(ownerID, level, index) { + if (index === (level ? 1 << level : 0) || this.array.length === 0) { + return this; + } + var sizeIndex = ((index - 1) >>> level) & MASK; + if (sizeIndex >= this.array.length) { + return this; + } + + var newChild; + if (level > 0) { + var oldChild = this.array[sizeIndex]; + newChild = oldChild && oldChild.removeAfter(ownerID, level - SHIFT, index); + if (newChild === oldChild && sizeIndex === this.array.length - 1) { + return this; + } + } + + var editable = editableVNode(this, ownerID); + editable.array.splice(sizeIndex + 1); + if (newChild) { + editable.array[sizeIndex] = newChild; + } + return editable; + }; + + + + var DONE = {}; + + function iterateList(list, reverse) { + var left = list._origin; + var right = list._capacity; + var tailPos = getTailOffset(right); + var tail = list._tail; + + return iterateNodeOrLeaf(list._root, list._level, 0); + + function iterateNodeOrLeaf(node, level, offset) { + return level === 0 ? + iterateLeaf(node, offset) : + iterateNode(node, level, offset); + } + + function iterateLeaf(node, offset) { + var array = offset === tailPos ? tail && tail.array : node && node.array; + var from = offset > left ? 0 : left - offset; + var to = right - offset; + if (to > SIZE) { + to = SIZE; + } + return function() { + if (from === to) { + return DONE; + } + var idx = reverse ? --to : from++; + return array && array[idx]; + }; + } + + function iterateNode(node, level, offset) { + var values; + var array = node && node.array; + var from = offset > left ? 0 : (left - offset) >> level; + var to = ((right - offset) >> level) + 1; + if (to > SIZE) { + to = SIZE; + } + return function() { + do { + if (values) { + var value = values(); + if (value !== DONE) { + return value; + } + values = null; + } + if (from === to) { + return DONE; + } + var idx = reverse ? --to : from++; + values = iterateNodeOrLeaf( + array && array[idx], level - SHIFT, offset + (idx << level) + ); + } while (true); + }; + } + } + + function makeList(origin, capacity, level, root, tail, ownerID, hash) { + var list = Object.create(ListPrototype); + list.size = capacity - origin; + list._origin = origin; + list._capacity = capacity; + list._level = level; + list._root = root; + list._tail = tail; + list.__ownerID = ownerID; + list.__hash = hash; + list.__altered = false; + return list; + } + + var EMPTY_LIST; + function emptyList() { + return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT)); + } + + function updateList(list, index, value) { + index = wrapIndex(list, index); + + if (index !== index) { + return list; + } + + if (index >= list.size || index < 0) { + return list.withMutations(function(list ) { + index < 0 ? + setListBounds(list, index).set(0, value) : + setListBounds(list, 0, index + 1).set(index, value) + }); + } + + index += list._origin; + + var newTail = list._tail; + var newRoot = list._root; + var didAlter = MakeRef(DID_ALTER); + if (index >= getTailOffset(list._capacity)) { + newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter); + } else { + newRoot = updateVNode(newRoot, list.__ownerID, list._level, index, value, didAlter); + } + + if (!didAlter.value) { + return list; + } + + if (list.__ownerID) { + list._root = newRoot; + list._tail = newTail; + list.__hash = undefined; + list.__altered = true; + return list; + } + return makeList(list._origin, list._capacity, list._level, newRoot, newTail); + } + + function updateVNode(node, ownerID, level, index, value, didAlter) { + var idx = (index >>> level) & MASK; + var nodeHas = node && idx < node.array.length; + if (!nodeHas && value === undefined) { + return node; + } + + var newNode; + + if (level > 0) { + var lowerNode = node && node.array[idx]; + var newLowerNode = updateVNode(lowerNode, ownerID, level - SHIFT, index, value, didAlter); + if (newLowerNode === lowerNode) { + return node; + } + newNode = editableVNode(node, ownerID); + newNode.array[idx] = newLowerNode; + return newNode; + } + + if (nodeHas && node.array[idx] === value) { + return node; + } + + SetRef(didAlter); + + newNode = editableVNode(node, ownerID); + if (value === undefined && idx === newNode.array.length - 1) { + newNode.array.pop(); + } else { + newNode.array[idx] = value; + } + return newNode; + } + + function editableVNode(node, ownerID) { + if (ownerID && node && ownerID === node.ownerID) { + return node; + } + return new VNode(node ? node.array.slice() : [], ownerID); + } + + function listNodeFor(list, rawIndex) { + if (rawIndex >= getTailOffset(list._capacity)) { + return list._tail; + } + if (rawIndex < 1 << (list._level + SHIFT)) { + var node = list._root; + var level = list._level; + while (node && level > 0) { + node = node.array[(rawIndex >>> level) & MASK]; + level -= SHIFT; + } + return node; + } + } + + function setListBounds(list, begin, end) { + // Sanitize begin & end using this shorthand for ToInt32(argument) + // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32 + if (begin !== undefined) { + begin = begin | 0; + } + if (end !== undefined) { + end = end | 0; + } + var owner = list.__ownerID || new OwnerID(); + var oldOrigin = list._origin; + var oldCapacity = list._capacity; + var newOrigin = oldOrigin + begin; + var newCapacity = end === undefined ? oldCapacity : end < 0 ? oldCapacity + end : oldOrigin + end; + if (newOrigin === oldOrigin && newCapacity === oldCapacity) { + return list; + } + + // If it's going to end after it starts, it's empty. + if (newOrigin >= newCapacity) { + return list.clear(); + } + + var newLevel = list._level; + var newRoot = list._root; + + // New origin might need creating a higher root. + var offsetShift = 0; + while (newOrigin + offsetShift < 0) { + newRoot = new VNode(newRoot && newRoot.array.length ? [undefined, newRoot] : [], owner); + newLevel += SHIFT; + offsetShift += 1 << newLevel; + } + if (offsetShift) { + newOrigin += offsetShift; + oldOrigin += offsetShift; + newCapacity += offsetShift; + oldCapacity += offsetShift; + } + + var oldTailOffset = getTailOffset(oldCapacity); + var newTailOffset = getTailOffset(newCapacity); + + // New size might need creating a higher root. + while (newTailOffset >= 1 << (newLevel + SHIFT)) { + newRoot = new VNode(newRoot && newRoot.array.length ? [newRoot] : [], owner); + newLevel += SHIFT; + } + + // Locate or create the new tail. + var oldTail = list._tail; + var newTail = newTailOffset < oldTailOffset ? + listNodeFor(list, newCapacity - 1) : + newTailOffset > oldTailOffset ? new VNode([], owner) : oldTail; + + // Merge Tail into tree. + if (oldTail && newTailOffset > oldTailOffset && newOrigin < oldCapacity && oldTail.array.length) { + newRoot = editableVNode(newRoot, owner); + var node = newRoot; + for (var level = newLevel; level > SHIFT; level -= SHIFT) { + var idx = (oldTailOffset >>> level) & MASK; + node = node.array[idx] = editableVNode(node.array[idx], owner); + } + node.array[(oldTailOffset >>> SHIFT) & MASK] = oldTail; + } + + // If the size has been reduced, there's a chance the tail needs to be trimmed. + if (newCapacity < oldCapacity) { + newTail = newTail && newTail.removeAfter(owner, 0, newCapacity); + } + + // If the new origin is within the tail, then we do not need a root. + if (newOrigin >= newTailOffset) { + newOrigin -= newTailOffset; + newCapacity -= newTailOffset; + newLevel = SHIFT; + newRoot = null; + newTail = newTail && newTail.removeBefore(owner, 0, newOrigin); + + // Otherwise, if the root has been trimmed, garbage collect. + } else if (newOrigin > oldOrigin || newTailOffset < oldTailOffset) { + offsetShift = 0; + + // Identify the new top root node of the subtree of the old root. + while (newRoot) { + var beginIndex = (newOrigin >>> newLevel) & MASK; + if (beginIndex !== (newTailOffset >>> newLevel) & MASK) { + break; + } + if (beginIndex) { + offsetShift += (1 << newLevel) * beginIndex; + } + newLevel -= SHIFT; + newRoot = newRoot.array[beginIndex]; + } + + // Trim the new sides of the new root. + if (newRoot && newOrigin > oldOrigin) { + newRoot = newRoot.removeBefore(owner, newLevel, newOrigin - offsetShift); + } + if (newRoot && newTailOffset < oldTailOffset) { + newRoot = newRoot.removeAfter(owner, newLevel, newTailOffset - offsetShift); + } + if (offsetShift) { + newOrigin -= offsetShift; + newCapacity -= offsetShift; + } + } + + if (list.__ownerID) { + list.size = newCapacity - newOrigin; + list._origin = newOrigin; + list._capacity = newCapacity; + list._level = newLevel; + list._root = newRoot; + list._tail = newTail; + list.__hash = undefined; + list.__altered = true; + return list; + } + return makeList(newOrigin, newCapacity, newLevel, newRoot, newTail); + } + + function mergeIntoListWith(list, merger, iterables) { + var iters = []; + var maxSize = 0; + for (var ii = 0; ii < iterables.length; ii++) { + var value = iterables[ii]; + var iter = IndexedIterable(value); + if (iter.size > maxSize) { + maxSize = iter.size; + } + if (!isIterable(value)) { + iter = iter.map(function(v ) {return fromJS(v)}); + } + iters.push(iter); + } + if (maxSize > list.size) { + list = list.setSize(maxSize); + } + return mergeIntoCollectionWith(list, merger, iters); + } + + function getTailOffset(size) { + return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT); + } + + createClass(OrderedMap, Map); + + // @pragma Construction + + function OrderedMap(value) { + return value === null || value === undefined ? emptyOrderedMap() : + isOrderedMap(value) ? value : + emptyOrderedMap().withMutations(function(map ) { + var iter = KeyedIterable(value); + assertNotInfinite(iter.size); + iter.forEach(function(v, k) {return map.set(k, v)}); + }); + } + + OrderedMap.of = function(/*...values*/) { + return this(arguments); + }; + + OrderedMap.prototype.toString = function() { + return this.__toString('OrderedMap {', '}'); + }; + + // @pragma Access + + OrderedMap.prototype.get = function(k, notSetValue) { + var index = this._map.get(k); + return index !== undefined ? this._list.get(index)[1] : notSetValue; + }; + + // @pragma Modification + + OrderedMap.prototype.clear = function() { + if (this.size === 0) { + return this; + } + if (this.__ownerID) { + this.size = 0; + this._map.clear(); + this._list.clear(); + return this; + } + return emptyOrderedMap(); + }; + + OrderedMap.prototype.set = function(k, v) { + return updateOrderedMap(this, k, v); + }; + + OrderedMap.prototype.remove = function(k) { + return updateOrderedMap(this, k, NOT_SET); + }; + + OrderedMap.prototype.wasAltered = function() { + return this._map.wasAltered() || this._list.wasAltered(); + }; + + OrderedMap.prototype.__iterate = function(fn, reverse) {var this$0 = this; + return this._list.__iterate( + function(entry ) {return entry && fn(entry[1], entry[0], this$0)}, + reverse + ); + }; + + OrderedMap.prototype.__iterator = function(type, reverse) { + return this._list.fromEntrySeq().__iterator(type, reverse); + }; + + OrderedMap.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + var newMap = this._map.__ensureOwner(ownerID); + var newList = this._list.__ensureOwner(ownerID); + if (!ownerID) { + this.__ownerID = ownerID; + this._map = newMap; + this._list = newList; + return this; + } + return makeOrderedMap(newMap, newList, ownerID, this.__hash); + }; + + + function isOrderedMap(maybeOrderedMap) { + return isMap(maybeOrderedMap) && isOrdered(maybeOrderedMap); + } + + OrderedMap.isOrderedMap = isOrderedMap; + + OrderedMap.prototype[IS_ORDERED_SENTINEL] = true; + OrderedMap.prototype[DELETE] = OrderedMap.prototype.remove; + + + + function makeOrderedMap(map, list, ownerID, hash) { + var omap = Object.create(OrderedMap.prototype); + omap.size = map ? map.size : 0; + omap._map = map; + omap._list = list; + omap.__ownerID = ownerID; + omap.__hash = hash; + return omap; + } + + var EMPTY_ORDERED_MAP; + function emptyOrderedMap() { + return EMPTY_ORDERED_MAP || (EMPTY_ORDERED_MAP = makeOrderedMap(emptyMap(), emptyList())); + } + + function updateOrderedMap(omap, k, v) { + var map = omap._map; + var list = omap._list; + var i = map.get(k); + var has = i !== undefined; + var newMap; + var newList; + if (v === NOT_SET) { // removed + if (!has) { + return omap; + } + if (list.size >= SIZE && list.size >= map.size * 2) { + newList = list.filter(function(entry, idx) {return entry !== undefined && i !== idx}); + newMap = newList.toKeyedSeq().map(function(entry ) {return entry[0]}).flip().toMap(); + if (omap.__ownerID) { + newMap.__ownerID = newList.__ownerID = omap.__ownerID; + } + } else { + newMap = map.remove(k); + newList = i === list.size - 1 ? list.pop() : list.set(i, undefined); + } + } else { + if (has) { + if (v === list.get(i)[1]) { + return omap; + } + newMap = map; + newList = list.set(i, [k, v]); + } else { + newMap = map.set(k, list.size); + newList = list.set(list.size, [k, v]); + } + } + if (omap.__ownerID) { + omap.size = newMap.size; + omap._map = newMap; + omap._list = newList; + omap.__hash = undefined; + return omap; + } + return makeOrderedMap(newMap, newList); + } + + createClass(ToKeyedSequence, KeyedSeq); + function ToKeyedSequence(indexed, useKeys) { + this._iter = indexed; + this._useKeys = useKeys; + this.size = indexed.size; + } + + ToKeyedSequence.prototype.get = function(key, notSetValue) { + return this._iter.get(key, notSetValue); + }; + + ToKeyedSequence.prototype.has = function(key) { + return this._iter.has(key); + }; + + ToKeyedSequence.prototype.valueSeq = function() { + return this._iter.valueSeq(); + }; + + ToKeyedSequence.prototype.reverse = function() {var this$0 = this; + var reversedSequence = reverseFactory(this, true); + if (!this._useKeys) { + reversedSequence.valueSeq = function() {return this$0._iter.toSeq().reverse()}; + } + return reversedSequence; + }; + + ToKeyedSequence.prototype.map = function(mapper, context) {var this$0 = this; + var mappedSequence = mapFactory(this, mapper, context); + if (!this._useKeys) { + mappedSequence.valueSeq = function() {return this$0._iter.toSeq().map(mapper, context)}; + } + return mappedSequence; + }; + + ToKeyedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this; + var ii; + return this._iter.__iterate( + this._useKeys ? + function(v, k) {return fn(v, k, this$0)} : + ((ii = reverse ? resolveSize(this) : 0), + function(v ) {return fn(v, reverse ? --ii : ii++, this$0)}), + reverse + ); + }; + + ToKeyedSequence.prototype.__iterator = function(type, reverse) { + if (this._useKeys) { + return this._iter.__iterator(type, reverse); + } + var iterator = this._iter.__iterator(ITERATE_VALUES, reverse); + var ii = reverse ? resolveSize(this) : 0; + return new Iterator(function() { + var step = iterator.next(); + return step.done ? step : + iteratorValue(type, reverse ? --ii : ii++, step.value, step); + }); + }; + + ToKeyedSequence.prototype[IS_ORDERED_SENTINEL] = true; + + + createClass(ToIndexedSequence, IndexedSeq); + function ToIndexedSequence(iter) { + this._iter = iter; + this.size = iter.size; + } + + ToIndexedSequence.prototype.includes = function(value) { + return this._iter.includes(value); + }; + + ToIndexedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this; + var iterations = 0; + return this._iter.__iterate(function(v ) {return fn(v, iterations++, this$0)}, reverse); + }; + + ToIndexedSequence.prototype.__iterator = function(type, reverse) { + var iterator = this._iter.__iterator(ITERATE_VALUES, reverse); + var iterations = 0; + return new Iterator(function() { + var step = iterator.next(); + return step.done ? step : + iteratorValue(type, iterations++, step.value, step) + }); + }; + + + + createClass(ToSetSequence, SetSeq); + function ToSetSequence(iter) { + this._iter = iter; + this.size = iter.size; + } + + ToSetSequence.prototype.has = function(key) { + return this._iter.includes(key); + }; + + ToSetSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this; + return this._iter.__iterate(function(v ) {return fn(v, v, this$0)}, reverse); + }; + + ToSetSequence.prototype.__iterator = function(type, reverse) { + var iterator = this._iter.__iterator(ITERATE_VALUES, reverse); + return new Iterator(function() { + var step = iterator.next(); + return step.done ? step : + iteratorValue(type, step.value, step.value, step); + }); + }; + + + + createClass(FromEntriesSequence, KeyedSeq); + function FromEntriesSequence(entries) { + this._iter = entries; + this.size = entries.size; + } + + FromEntriesSequence.prototype.entrySeq = function() { + return this._iter.toSeq(); + }; + + FromEntriesSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this; + return this._iter.__iterate(function(entry ) { + // Check if entry exists first so array access doesn't throw for holes + // in the parent iteration. + if (entry) { + validateEntry(entry); + var indexedIterable = isIterable(entry); + return fn( + indexedIterable ? entry.get(1) : entry[1], + indexedIterable ? entry.get(0) : entry[0], + this$0 + ); + } + }, reverse); + }; + + FromEntriesSequence.prototype.__iterator = function(type, reverse) { + var iterator = this._iter.__iterator(ITERATE_VALUES, reverse); + return new Iterator(function() { + while (true) { + var step = iterator.next(); + if (step.done) { + return step; + } + var entry = step.value; + // Check if entry exists first so array access doesn't throw for holes + // in the parent iteration. + if (entry) { + validateEntry(entry); + var indexedIterable = isIterable(entry); + return iteratorValue( + type, + indexedIterable ? entry.get(0) : entry[0], + indexedIterable ? entry.get(1) : entry[1], + step + ); + } + } + }); + }; + + + ToIndexedSequence.prototype.cacheResult = + ToKeyedSequence.prototype.cacheResult = + ToSetSequence.prototype.cacheResult = + FromEntriesSequence.prototype.cacheResult = + cacheResultThrough; + + + function flipFactory(iterable) { + var flipSequence = makeSequence(iterable); + flipSequence._iter = iterable; + flipSequence.size = iterable.size; + flipSequence.flip = function() {return iterable}; + flipSequence.reverse = function () { + var reversedSequence = iterable.reverse.apply(this); // super.reverse() + reversedSequence.flip = function() {return iterable.reverse()}; + return reversedSequence; + }; + flipSequence.has = function(key ) {return iterable.includes(key)}; + flipSequence.includes = function(key ) {return iterable.has(key)}; + flipSequence.cacheResult = cacheResultThrough; + flipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this; + return iterable.__iterate(function(v, k) {return fn(k, v, this$0) !== false}, reverse); + } + flipSequence.__iteratorUncached = function(type, reverse) { + if (type === ITERATE_ENTRIES) { + var iterator = iterable.__iterator(type, reverse); + return new Iterator(function() { + var step = iterator.next(); + if (!step.done) { + var k = step.value[0]; + step.value[0] = step.value[1]; + step.value[1] = k; + } + return step; + }); + } + return iterable.__iterator( + type === ITERATE_VALUES ? ITERATE_KEYS : ITERATE_VALUES, + reverse + ); + } + return flipSequence; + } + + + function mapFactory(iterable, mapper, context) { + var mappedSequence = makeSequence(iterable); + mappedSequence.size = iterable.size; + mappedSequence.has = function(key ) {return iterable.has(key)}; + mappedSequence.get = function(key, notSetValue) { + var v = iterable.get(key, NOT_SET); + return v === NOT_SET ? + notSetValue : + mapper.call(context, v, key, iterable); + }; + mappedSequence.__iterateUncached = function (fn, reverse) {var this$0 = this; + return iterable.__iterate( + function(v, k, c) {return fn(mapper.call(context, v, k, c), k, this$0) !== false}, + reverse + ); + } + mappedSequence.__iteratorUncached = function (type, reverse) { + var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse); + return new Iterator(function() { + var step = iterator.next(); + if (step.done) { + return step; + } + var entry = step.value; + var key = entry[0]; + return iteratorValue( + type, + key, + mapper.call(context, entry[1], key, iterable), + step + ); + }); + } + return mappedSequence; + } + + + function reverseFactory(iterable, useKeys) { + var reversedSequence = makeSequence(iterable); + reversedSequence._iter = iterable; + reversedSequence.size = iterable.size; + reversedSequence.reverse = function() {return iterable}; + if (iterable.flip) { + reversedSequence.flip = function () { + var flipSequence = flipFactory(iterable); + flipSequence.reverse = function() {return iterable.flip()}; + return flipSequence; + }; + } + reversedSequence.get = function(key, notSetValue) + {return iterable.get(useKeys ? key : -1 - key, notSetValue)}; + reversedSequence.has = function(key ) + {return iterable.has(useKeys ? key : -1 - key)}; + reversedSequence.includes = function(value ) {return iterable.includes(value)}; + reversedSequence.cacheResult = cacheResultThrough; + reversedSequence.__iterate = function (fn, reverse) {var this$0 = this; + return iterable.__iterate(function(v, k) {return fn(v, k, this$0)}, !reverse); + }; + reversedSequence.__iterator = + function(type, reverse) {return iterable.__iterator(type, !reverse)}; + return reversedSequence; + } + + + function filterFactory(iterable, predicate, context, useKeys) { + var filterSequence = makeSequence(iterable); + if (useKeys) { + filterSequence.has = function(key ) { + var v = iterable.get(key, NOT_SET); + return v !== NOT_SET && !!predicate.call(context, v, key, iterable); + }; + filterSequence.get = function(key, notSetValue) { + var v = iterable.get(key, NOT_SET); + return v !== NOT_SET && predicate.call(context, v, key, iterable) ? + v : notSetValue; + }; + } + filterSequence.__iterateUncached = function (fn, reverse) {var this$0 = this; + var iterations = 0; + iterable.__iterate(function(v, k, c) { + if (predicate.call(context, v, k, c)) { + iterations++; + return fn(v, useKeys ? k : iterations - 1, this$0); + } + }, reverse); + return iterations; + }; + filterSequence.__iteratorUncached = function (type, reverse) { + var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse); + var iterations = 0; + return new Iterator(function() { + while (true) { + var step = iterator.next(); + if (step.done) { + return step; + } + var entry = step.value; + var key = entry[0]; + var value = entry[1]; + if (predicate.call(context, value, key, iterable)) { + return iteratorValue(type, useKeys ? key : iterations++, value, step); + } + } + }); + } + return filterSequence; + } + + + function countByFactory(iterable, grouper, context) { + var groups = Map().asMutable(); + iterable.__iterate(function(v, k) { + groups.update( + grouper.call(context, v, k, iterable), + 0, + function(a ) {return a + 1} + ); + }); + return groups.asImmutable(); + } + + + function groupByFactory(iterable, grouper, context) { + var isKeyedIter = isKeyed(iterable); + var groups = (isOrdered(iterable) ? OrderedMap() : Map()).asMutable(); + iterable.__iterate(function(v, k) { + groups.update( + grouper.call(context, v, k, iterable), + function(a ) {return (a = a || [], a.push(isKeyedIter ? [k, v] : v), a)} + ); + }); + var coerce = iterableClass(iterable); + return groups.map(function(arr ) {return reify(iterable, coerce(arr))}); + } + + + function sliceFactory(iterable, begin, end, useKeys) { + var originalSize = iterable.size; + + // Sanitize begin & end using this shorthand for ToInt32(argument) + // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32 + if (begin !== undefined) { + begin = begin | 0; + } + if (end !== undefined) { + if (end === Infinity) { + end = originalSize; + } else { + end = end | 0; + } + } + + if (wholeSlice(begin, end, originalSize)) { + return iterable; + } + + var resolvedBegin = resolveBegin(begin, originalSize); + var resolvedEnd = resolveEnd(end, originalSize); + + // begin or end will be NaN if they were provided as negative numbers and + // this iterable's size is unknown. In that case, cache first so there is + // a known size and these do not resolve to NaN. + if (resolvedBegin !== resolvedBegin || resolvedEnd !== resolvedEnd) { + return sliceFactory(iterable.toSeq().cacheResult(), begin, end, useKeys); + } + + // Note: resolvedEnd is undefined when the original sequence's length is + // unknown and this slice did not supply an end and should contain all + // elements after resolvedBegin. + // In that case, resolvedSize will be NaN and sliceSize will remain undefined. + var resolvedSize = resolvedEnd - resolvedBegin; + var sliceSize; + if (resolvedSize === resolvedSize) { + sliceSize = resolvedSize < 0 ? 0 : resolvedSize; + } + + var sliceSeq = makeSequence(iterable); + + // If iterable.size is undefined, the size of the realized sliceSeq is + // unknown at this point unless the number of items to slice is 0 + sliceSeq.size = sliceSize === 0 ? sliceSize : iterable.size && sliceSize || undefined; + + if (!useKeys && isSeq(iterable) && sliceSize >= 0) { + sliceSeq.get = function (index, notSetValue) { + index = wrapIndex(this, index); + return index >= 0 && index < sliceSize ? + iterable.get(index + resolvedBegin, notSetValue) : + notSetValue; + } + } + + sliceSeq.__iterateUncached = function(fn, reverse) {var this$0 = this; + if (sliceSize === 0) { + return 0; + } + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + var skipped = 0; + var isSkipping = true; + var iterations = 0; + iterable.__iterate(function(v, k) { + if (!(isSkipping && (isSkipping = skipped++ < resolvedBegin))) { + iterations++; + return fn(v, useKeys ? k : iterations - 1, this$0) !== false && + iterations !== sliceSize; + } + }); + return iterations; + }; + + sliceSeq.__iteratorUncached = function(type, reverse) { + if (sliceSize !== 0 && reverse) { + return this.cacheResult().__iterator(type, reverse); + } + // Don't bother instantiating parent iterator if taking 0. + var iterator = sliceSize !== 0 && iterable.__iterator(type, reverse); + var skipped = 0; + var iterations = 0; + return new Iterator(function() { + while (skipped++ < resolvedBegin) { + iterator.next(); + } + if (++iterations > sliceSize) { + return iteratorDone(); + } + var step = iterator.next(); + if (useKeys || type === ITERATE_VALUES) { + return step; + } else if (type === ITERATE_KEYS) { + return iteratorValue(type, iterations - 1, undefined, step); + } else { + return iteratorValue(type, iterations - 1, step.value[1], step); + } + }); + } + + return sliceSeq; + } + + + function takeWhileFactory(iterable, predicate, context) { + var takeSequence = makeSequence(iterable); + takeSequence.__iterateUncached = function(fn, reverse) {var this$0 = this; + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + var iterations = 0; + iterable.__iterate(function(v, k, c) + {return predicate.call(context, v, k, c) && ++iterations && fn(v, k, this$0)} + ); + return iterations; + }; + takeSequence.__iteratorUncached = function(type, reverse) {var this$0 = this; + if (reverse) { + return this.cacheResult().__iterator(type, reverse); + } + var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse); + var iterating = true; + return new Iterator(function() { + if (!iterating) { + return iteratorDone(); + } + var step = iterator.next(); + if (step.done) { + return step; + } + var entry = step.value; + var k = entry[0]; + var v = entry[1]; + if (!predicate.call(context, v, k, this$0)) { + iterating = false; + return iteratorDone(); + } + return type === ITERATE_ENTRIES ? step : + iteratorValue(type, k, v, step); + }); + }; + return takeSequence; + } + + + function skipWhileFactory(iterable, predicate, context, useKeys) { + var skipSequence = makeSequence(iterable); + skipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this; + if (reverse) { + return this.cacheResult().__iterate(fn, reverse); + } + var isSkipping = true; + var iterations = 0; + iterable.__iterate(function(v, k, c) { + if (!(isSkipping && (isSkipping = predicate.call(context, v, k, c)))) { + iterations++; + return fn(v, useKeys ? k : iterations - 1, this$0); + } + }); + return iterations; + }; + skipSequence.__iteratorUncached = function(type, reverse) {var this$0 = this; + if (reverse) { + return this.cacheResult().__iterator(type, reverse); + } + var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse); + var skipping = true; + var iterations = 0; + return new Iterator(function() { + var step, k, v; + do { + step = iterator.next(); + if (step.done) { + if (useKeys || type === ITERATE_VALUES) { + return step; + } else if (type === ITERATE_KEYS) { + return iteratorValue(type, iterations++, undefined, step); + } else { + return iteratorValue(type, iterations++, step.value[1], step); + } + } + var entry = step.value; + k = entry[0]; + v = entry[1]; + skipping && (skipping = predicate.call(context, v, k, this$0)); + } while (skipping); + return type === ITERATE_ENTRIES ? step : + iteratorValue(type, k, v, step); + }); + }; + return skipSequence; + } + + + function concatFactory(iterable, values) { + var isKeyedIterable = isKeyed(iterable); + var iters = [iterable].concat(values).map(function(v ) { + if (!isIterable(v)) { + v = isKeyedIterable ? + keyedSeqFromValue(v) : + indexedSeqFromValue(Array.isArray(v) ? v : [v]); + } else if (isKeyedIterable) { + v = KeyedIterable(v); + } + return v; + }).filter(function(v ) {return v.size !== 0}); + + if (iters.length === 0) { + return iterable; + } + + if (iters.length === 1) { + var singleton = iters[0]; + if (singleton === iterable || + isKeyedIterable && isKeyed(singleton) || + isIndexed(iterable) && isIndexed(singleton)) { + return singleton; + } + } + + var concatSeq = new ArraySeq(iters); + if (isKeyedIterable) { + concatSeq = concatSeq.toKeyedSeq(); + } else if (!isIndexed(iterable)) { + concatSeq = concatSeq.toSetSeq(); + } + concatSeq = concatSeq.flatten(true); + concatSeq.size = iters.reduce( + function(sum, seq) { + if (sum !== undefined) { + var size = seq.size; + if (size !== undefined) { + return sum + size; + } + } + }, + 0 + ); + return concatSeq; + } + + + function flattenFactory(iterable, depth, useKeys) { + var flatSequence = makeSequence(iterable); + flatSequence.__iterateUncached = function(fn, reverse) { + var iterations = 0; + var stopped = false; + function flatDeep(iter, currentDepth) {var this$0 = this; + iter.__iterate(function(v, k) { + if ((!depth || currentDepth < depth) && isIterable(v)) { + flatDeep(v, currentDepth + 1); + } else if (fn(v, useKeys ? k : iterations++, this$0) === false) { + stopped = true; + } + return !stopped; + }, reverse); + } + flatDeep(iterable, 0); + return iterations; + } + flatSequence.__iteratorUncached = function(type, reverse) { + var iterator = iterable.__iterator(type, reverse); + var stack = []; + var iterations = 0; + return new Iterator(function() { + while (iterator) { + var step = iterator.next(); + if (step.done !== false) { + iterator = stack.pop(); + continue; + } + var v = step.value; + if (type === ITERATE_ENTRIES) { + v = v[1]; + } + if ((!depth || stack.length < depth) && isIterable(v)) { + stack.push(iterator); + iterator = v.__iterator(type, reverse); + } else { + return useKeys ? step : iteratorValue(type, iterations++, v, step); + } + } + return iteratorDone(); + }); + } + return flatSequence; + } + + + function flatMapFactory(iterable, mapper, context) { + var coerce = iterableClass(iterable); + return iterable.toSeq().map( + function(v, k) {return coerce(mapper.call(context, v, k, iterable))} + ).flatten(true); + } + + + function interposeFactory(iterable, separator) { + var interposedSequence = makeSequence(iterable); + interposedSequence.size = iterable.size && iterable.size * 2 -1; + interposedSequence.__iterateUncached = function(fn, reverse) {var this$0 = this; + var iterations = 0; + iterable.__iterate(function(v, k) + {return (!iterations || fn(separator, iterations++, this$0) !== false) && + fn(v, iterations++, this$0) !== false}, + reverse + ); + return iterations; + }; + interposedSequence.__iteratorUncached = function(type, reverse) { + var iterator = iterable.__iterator(ITERATE_VALUES, reverse); + var iterations = 0; + var step; + return new Iterator(function() { + if (!step || iterations % 2) { + step = iterator.next(); + if (step.done) { + return step; + } + } + return iterations % 2 ? + iteratorValue(type, iterations++, separator) : + iteratorValue(type, iterations++, step.value, step); + }); + }; + return interposedSequence; + } + + + function sortFactory(iterable, comparator, mapper) { + if (!comparator) { + comparator = defaultComparator; + } + var isKeyedIterable = isKeyed(iterable); + var index = 0; + var entries = iterable.toSeq().map( + function(v, k) {return [k, v, index++, mapper ? mapper(v, k, iterable) : v]} + ).toArray(); + entries.sort(function(a, b) {return comparator(a[3], b[3]) || a[2] - b[2]}).forEach( + isKeyedIterable ? + function(v, i) { entries[i].length = 2; } : + function(v, i) { entries[i] = v[1]; } + ); + return isKeyedIterable ? KeyedSeq(entries) : + isIndexed(iterable) ? IndexedSeq(entries) : + SetSeq(entries); + } + + + function maxFactory(iterable, comparator, mapper) { + if (!comparator) { + comparator = defaultComparator; + } + if (mapper) { + var entry = iterable.toSeq() + .map(function(v, k) {return [v, mapper(v, k, iterable)]}) + .reduce(function(a, b) {return maxCompare(comparator, a[1], b[1]) ? b : a}); + return entry && entry[0]; + } else { + return iterable.reduce(function(a, b) {return maxCompare(comparator, a, b) ? b : a}); + } + } + + function maxCompare(comparator, a, b) { + var comp = comparator(b, a); + // b is considered the new max if the comparator declares them equal, but + // they are not equal and b is in fact a nullish value. + return (comp === 0 && b !== a && (b === undefined || b === null || b !== b)) || comp > 0; + } + + + function zipWithFactory(keyIter, zipper, iters) { + var zipSequence = makeSequence(keyIter); + zipSequence.size = new ArraySeq(iters).map(function(i ) {return i.size}).min(); + // Note: this a generic base implementation of __iterate in terms of + // __iterator which may be more generically useful in the future. + zipSequence.__iterate = function(fn, reverse) { + /* generic: + var iterator = this.__iterator(ITERATE_ENTRIES, reverse); + var step; + var iterations = 0; + while (!(step = iterator.next()).done) { + iterations++; + if (fn(step.value[1], step.value[0], this) === false) { + break; + } + } + return iterations; + */ + // indexed: + var iterator = this.__iterator(ITERATE_VALUES, reverse); + var step; + var iterations = 0; + while (!(step = iterator.next()).done) { + if (fn(step.value, iterations++, this) === false) { + break; + } + } + return iterations; + }; + zipSequence.__iteratorUncached = function(type, reverse) { + var iterators = iters.map(function(i ) + {return (i = Iterable(i), getIterator(reverse ? i.reverse() : i))} + ); + var iterations = 0; + var isDone = false; + return new Iterator(function() { + var steps; + if (!isDone) { + steps = iterators.map(function(i ) {return i.next()}); + isDone = steps.some(function(s ) {return s.done}); + } + if (isDone) { + return iteratorDone(); + } + return iteratorValue( + type, + iterations++, + zipper.apply(null, steps.map(function(s ) {return s.value})) + ); + }); + }; + return zipSequence + } + + + // #pragma Helper Functions + + function reify(iter, seq) { + return isSeq(iter) ? seq : iter.constructor(seq); + } + + function validateEntry(entry) { + if (entry !== Object(entry)) { + throw new TypeError('Expected [K, V] tuple: ' + entry); + } + } + + function resolveSize(iter) { + assertNotInfinite(iter.size); + return ensureSize(iter); + } + + function iterableClass(iterable) { + return isKeyed(iterable) ? KeyedIterable : + isIndexed(iterable) ? IndexedIterable : + SetIterable; + } + + function makeSequence(iterable) { + return Object.create( + ( + isKeyed(iterable) ? KeyedSeq : + isIndexed(iterable) ? IndexedSeq : + SetSeq + ).prototype + ); + } + + function cacheResultThrough() { + if (this._iter.cacheResult) { + this._iter.cacheResult(); + this.size = this._iter.size; + return this; + } else { + return Seq.prototype.cacheResult.call(this); + } + } + + function defaultComparator(a, b) { + return a > b ? 1 : a < b ? -1 : 0; + } + + function forceIterator(keyPath) { + var iter = getIterator(keyPath); + if (!iter) { + // Array might not be iterable in this environment, so we need a fallback + // to our wrapped type. + if (!isArrayLike(keyPath)) { + throw new TypeError('Expected iterable or array-like: ' + keyPath); + } + iter = getIterator(Iterable(keyPath)); + } + return iter; + } + + createClass(Record, KeyedCollection); + + function Record(defaultValues, name) { + var hasInitialized; + + var RecordType = function Record(values) { + if (values instanceof RecordType) { + return values; + } + if (!(this instanceof RecordType)) { + return new RecordType(values); + } + if (!hasInitialized) { + hasInitialized = true; + var keys = Object.keys(defaultValues); + setProps(RecordTypePrototype, keys); + RecordTypePrototype.size = keys.length; + RecordTypePrototype._name = name; + RecordTypePrototype._keys = keys; + RecordTypePrototype._defaultValues = defaultValues; + } + this._map = Map(values); + }; + + var RecordTypePrototype = RecordType.prototype = Object.create(RecordPrototype); + RecordTypePrototype.constructor = RecordType; + + return RecordType; + } + + Record.prototype.toString = function() { + return this.__toString(recordName(this) + ' {', '}'); + }; + + // @pragma Access + + Record.prototype.has = function(k) { + return this._defaultValues.hasOwnProperty(k); + }; + + Record.prototype.get = function(k, notSetValue) { + if (!this.has(k)) { + return notSetValue; + } + var defaultVal = this._defaultValues[k]; + return this._map ? this._map.get(k, defaultVal) : defaultVal; + }; + + // @pragma Modification + + Record.prototype.clear = function() { + if (this.__ownerID) { + this._map && this._map.clear(); + return this; + } + var RecordType = this.constructor; + return RecordType._empty || (RecordType._empty = makeRecord(this, emptyMap())); + }; + + Record.prototype.set = function(k, v) { + if (!this.has(k)) { + throw new Error('Cannot set unknown key "' + k + '" on ' + recordName(this)); + } + if (this._map && !this._map.has(k)) { + var defaultVal = this._defaultValues[k]; + if (v === defaultVal) { + return this; + } + } + var newMap = this._map && this._map.set(k, v); + if (this.__ownerID || newMap === this._map) { + return this; + } + return makeRecord(this, newMap); + }; + + Record.prototype.remove = function(k) { + if (!this.has(k)) { + return this; + } + var newMap = this._map && this._map.remove(k); + if (this.__ownerID || newMap === this._map) { + return this; + } + return makeRecord(this, newMap); + }; + + Record.prototype.wasAltered = function() { + return this._map.wasAltered(); + }; + + Record.prototype.__iterator = function(type, reverse) {var this$0 = this; + return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterator(type, reverse); + }; + + Record.prototype.__iterate = function(fn, reverse) {var this$0 = this; + return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterate(fn, reverse); + }; + + Record.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + var newMap = this._map && this._map.__ensureOwner(ownerID); + if (!ownerID) { + this.__ownerID = ownerID; + this._map = newMap; + return this; + } + return makeRecord(this, newMap, ownerID); + }; + + + var RecordPrototype = Record.prototype; + RecordPrototype[DELETE] = RecordPrototype.remove; + RecordPrototype.deleteIn = + RecordPrototype.removeIn = MapPrototype.removeIn; + RecordPrototype.merge = MapPrototype.merge; + RecordPrototype.mergeWith = MapPrototype.mergeWith; + RecordPrototype.mergeIn = MapPrototype.mergeIn; + RecordPrototype.mergeDeep = MapPrototype.mergeDeep; + RecordPrototype.mergeDeepWith = MapPrototype.mergeDeepWith; + RecordPrototype.mergeDeepIn = MapPrototype.mergeDeepIn; + RecordPrototype.setIn = MapPrototype.setIn; + RecordPrototype.update = MapPrototype.update; + RecordPrototype.updateIn = MapPrototype.updateIn; + RecordPrototype.withMutations = MapPrototype.withMutations; + RecordPrototype.asMutable = MapPrototype.asMutable; + RecordPrototype.asImmutable = MapPrototype.asImmutable; + + + function makeRecord(likeRecord, map, ownerID) { + var record = Object.create(Object.getPrototypeOf(likeRecord)); + record._map = map; + record.__ownerID = ownerID; + return record; + } + + function recordName(record) { + return record._name || record.constructor.name || 'Record'; + } + + function setProps(prototype, names) { + try { + names.forEach(setProp.bind(undefined, prototype)); + } catch (error) { + // Object.defineProperty failed. Probably IE8. + } + } + + function setProp(prototype, name) { + Object.defineProperty(prototype, name, { + get: function() { + return this.get(name); + }, + set: function(value) { + invariant(this.__ownerID, 'Cannot set on an immutable record.'); + this.set(name, value); + } + }); + } + + createClass(Set, SetCollection); + + // @pragma Construction + + function Set(value) { + return value === null || value === undefined ? emptySet() : + isSet(value) && !isOrdered(value) ? value : + emptySet().withMutations(function(set ) { + var iter = SetIterable(value); + assertNotInfinite(iter.size); + iter.forEach(function(v ) {return set.add(v)}); + }); + } + + Set.of = function(/*...values*/) { + return this(arguments); + }; + + Set.fromKeys = function(value) { + return this(KeyedIterable(value).keySeq()); + }; + + Set.prototype.toString = function() { + return this.__toString('Set {', '}'); + }; + + // @pragma Access + + Set.prototype.has = function(value) { + return this._map.has(value); + }; + + // @pragma Modification + + Set.prototype.add = function(value) { + return updateSet(this, this._map.set(value, true)); + }; + + Set.prototype.remove = function(value) { + return updateSet(this, this._map.remove(value)); + }; + + Set.prototype.clear = function() { + return updateSet(this, this._map.clear()); + }; + + // @pragma Composition + + Set.prototype.union = function() {var iters = SLICE$0.call(arguments, 0); + iters = iters.filter(function(x ) {return x.size !== 0}); + if (iters.length === 0) { + return this; + } + if (this.size === 0 && !this.__ownerID && iters.length === 1) { + return this.constructor(iters[0]); + } + return this.withMutations(function(set ) { + for (var ii = 0; ii < iters.length; ii++) { + SetIterable(iters[ii]).forEach(function(value ) {return set.add(value)}); + } + }); + }; + + Set.prototype.intersect = function() {var iters = SLICE$0.call(arguments, 0); + if (iters.length === 0) { + return this; + } + iters = iters.map(function(iter ) {return SetIterable(iter)}); + var originalSet = this; + return this.withMutations(function(set ) { + originalSet.forEach(function(value ) { + if (!iters.every(function(iter ) {return iter.includes(value)})) { + set.remove(value); + } + }); + }); + }; + + Set.prototype.subtract = function() {var iters = SLICE$0.call(arguments, 0); + if (iters.length === 0) { + return this; + } + iters = iters.map(function(iter ) {return SetIterable(iter)}); + var originalSet = this; + return this.withMutations(function(set ) { + originalSet.forEach(function(value ) { + if (iters.some(function(iter ) {return iter.includes(value)})) { + set.remove(value); + } + }); + }); + }; + + Set.prototype.merge = function() { + return this.union.apply(this, arguments); + }; + + Set.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1); + return this.union.apply(this, iters); + }; + + Set.prototype.sort = function(comparator) { + // Late binding + return OrderedSet(sortFactory(this, comparator)); + }; + + Set.prototype.sortBy = function(mapper, comparator) { + // Late binding + return OrderedSet(sortFactory(this, comparator, mapper)); + }; + + Set.prototype.wasAltered = function() { + return this._map.wasAltered(); + }; + + Set.prototype.__iterate = function(fn, reverse) {var this$0 = this; + return this._map.__iterate(function(_, k) {return fn(k, k, this$0)}, reverse); + }; + + Set.prototype.__iterator = function(type, reverse) { + return this._map.map(function(_, k) {return k}).__iterator(type, reverse); + }; + + Set.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + var newMap = this._map.__ensureOwner(ownerID); + if (!ownerID) { + this.__ownerID = ownerID; + this._map = newMap; + return this; + } + return this.__make(newMap, ownerID); + }; + + + function isSet(maybeSet) { + return !!(maybeSet && maybeSet[IS_SET_SENTINEL]); + } + + Set.isSet = isSet; + + var IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@'; + + var SetPrototype = Set.prototype; + SetPrototype[IS_SET_SENTINEL] = true; + SetPrototype[DELETE] = SetPrototype.remove; + SetPrototype.mergeDeep = SetPrototype.merge; + SetPrototype.mergeDeepWith = SetPrototype.mergeWith; + SetPrototype.withMutations = MapPrototype.withMutations; + SetPrototype.asMutable = MapPrototype.asMutable; + SetPrototype.asImmutable = MapPrototype.asImmutable; + + SetPrototype.__empty = emptySet; + SetPrototype.__make = makeSet; + + function updateSet(set, newMap) { + if (set.__ownerID) { + set.size = newMap.size; + set._map = newMap; + return set; + } + return newMap === set._map ? set : + newMap.size === 0 ? set.__empty() : + set.__make(newMap); + } + + function makeSet(map, ownerID) { + var set = Object.create(SetPrototype); + set.size = map ? map.size : 0; + set._map = map; + set.__ownerID = ownerID; + return set; + } + + var EMPTY_SET; + function emptySet() { + return EMPTY_SET || (EMPTY_SET = makeSet(emptyMap())); + } + + createClass(OrderedSet, Set); + + // @pragma Construction + + function OrderedSet(value) { + return value === null || value === undefined ? emptyOrderedSet() : + isOrderedSet(value) ? value : + emptyOrderedSet().withMutations(function(set ) { + var iter = SetIterable(value); + assertNotInfinite(iter.size); + iter.forEach(function(v ) {return set.add(v)}); + }); + } + + OrderedSet.of = function(/*...values*/) { + return this(arguments); + }; + + OrderedSet.fromKeys = function(value) { + return this(KeyedIterable(value).keySeq()); + }; + + OrderedSet.prototype.toString = function() { + return this.__toString('OrderedSet {', '}'); + }; + + + function isOrderedSet(maybeOrderedSet) { + return isSet(maybeOrderedSet) && isOrdered(maybeOrderedSet); + } + + OrderedSet.isOrderedSet = isOrderedSet; + + var OrderedSetPrototype = OrderedSet.prototype; + OrderedSetPrototype[IS_ORDERED_SENTINEL] = true; + + OrderedSetPrototype.__empty = emptyOrderedSet; + OrderedSetPrototype.__make = makeOrderedSet; + + function makeOrderedSet(map, ownerID) { + var set = Object.create(OrderedSetPrototype); + set.size = map ? map.size : 0; + set._map = map; + set.__ownerID = ownerID; + return set; + } + + var EMPTY_ORDERED_SET; + function emptyOrderedSet() { + return EMPTY_ORDERED_SET || (EMPTY_ORDERED_SET = makeOrderedSet(emptyOrderedMap())); + } + + createClass(Stack, IndexedCollection); + + // @pragma Construction + + function Stack(value) { + return value === null || value === undefined ? emptyStack() : + isStack(value) ? value : + emptyStack().unshiftAll(value); + } + + Stack.of = function(/*...values*/) { + return this(arguments); + }; + + Stack.prototype.toString = function() { + return this.__toString('Stack [', ']'); + }; + + // @pragma Access + + Stack.prototype.get = function(index, notSetValue) { + var head = this._head; + index = wrapIndex(this, index); + while (head && index--) { + head = head.next; + } + return head ? head.value : notSetValue; + }; + + Stack.prototype.peek = function() { + return this._head && this._head.value; + }; + + // @pragma Modification + + Stack.prototype.push = function(/*...values*/) { + if (arguments.length === 0) { + return this; + } + var newSize = this.size + arguments.length; + var head = this._head; + for (var ii = arguments.length - 1; ii >= 0; ii--) { + head = { + value: arguments[ii], + next: head + }; + } + if (this.__ownerID) { + this.size = newSize; + this._head = head; + this.__hash = undefined; + this.__altered = true; + return this; + } + return makeStack(newSize, head); + }; + + Stack.prototype.pushAll = function(iter) { + iter = IndexedIterable(iter); + if (iter.size === 0) { + return this; + } + assertNotInfinite(iter.size); + var newSize = this.size; + var head = this._head; + iter.reverse().forEach(function(value ) { + newSize++; + head = { + value: value, + next: head + }; + }); + if (this.__ownerID) { + this.size = newSize; + this._head = head; + this.__hash = undefined; + this.__altered = true; + return this; + } + return makeStack(newSize, head); + }; + + Stack.prototype.pop = function() { + return this.slice(1); + }; + + Stack.prototype.unshift = function(/*...values*/) { + return this.push.apply(this, arguments); + }; + + Stack.prototype.unshiftAll = function(iter) { + return this.pushAll(iter); + }; + + Stack.prototype.shift = function() { + return this.pop.apply(this, arguments); + }; + + Stack.prototype.clear = function() { + if (this.size === 0) { + return this; + } + if (this.__ownerID) { + this.size = 0; + this._head = undefined; + this.__hash = undefined; + this.__altered = true; + return this; + } + return emptyStack(); + }; + + Stack.prototype.slice = function(begin, end) { + if (wholeSlice(begin, end, this.size)) { + return this; + } + var resolvedBegin = resolveBegin(begin, this.size); + var resolvedEnd = resolveEnd(end, this.size); + if (resolvedEnd !== this.size) { + // super.slice(begin, end); + return IndexedCollection.prototype.slice.call(this, begin, end); + } + var newSize = this.size - resolvedBegin; + var head = this._head; + while (resolvedBegin--) { + head = head.next; + } + if (this.__ownerID) { + this.size = newSize; + this._head = head; + this.__hash = undefined; + this.__altered = true; + return this; + } + return makeStack(newSize, head); + }; + + // @pragma Mutability + + Stack.prototype.__ensureOwner = function(ownerID) { + if (ownerID === this.__ownerID) { + return this; + } + if (!ownerID) { + this.__ownerID = ownerID; + this.__altered = false; + return this; + } + return makeStack(this.size, this._head, ownerID, this.__hash); + }; + + // @pragma Iteration + + Stack.prototype.__iterate = function(fn, reverse) { + if (reverse) { + return this.reverse().__iterate(fn); + } + var iterations = 0; + var node = this._head; + while (node) { + if (fn(node.value, iterations++, this) === false) { + break; + } + node = node.next; + } + return iterations; + }; + + Stack.prototype.__iterator = function(type, reverse) { + if (reverse) { + return this.reverse().__iterator(type); + } + var iterations = 0; + var node = this._head; + return new Iterator(function() { + if (node) { + var value = node.value; + node = node.next; + return iteratorValue(type, iterations++, value); + } + return iteratorDone(); + }); + }; + + + function isStack(maybeStack) { + return !!(maybeStack && maybeStack[IS_STACK_SENTINEL]); + } + + Stack.isStack = isStack; + + var IS_STACK_SENTINEL = '@@__IMMUTABLE_STACK__@@'; + + var StackPrototype = Stack.prototype; + StackPrototype[IS_STACK_SENTINEL] = true; + StackPrototype.withMutations = MapPrototype.withMutations; + StackPrototype.asMutable = MapPrototype.asMutable; + StackPrototype.asImmutable = MapPrototype.asImmutable; + StackPrototype.wasAltered = MapPrototype.wasAltered; + + + function makeStack(size, head, ownerID, hash) { + var map = Object.create(StackPrototype); + map.size = size; + map._head = head; + map.__ownerID = ownerID; + map.__hash = hash; + map.__altered = false; + return map; + } + + var EMPTY_STACK; + function emptyStack() { + return EMPTY_STACK || (EMPTY_STACK = makeStack(0)); + } + + /** + * Contributes additional methods to a constructor + */ + function mixin(ctor, methods) { + var keyCopier = function(key ) { ctor.prototype[key] = methods[key]; }; + Object.keys(methods).forEach(keyCopier); + Object.getOwnPropertySymbols && + Object.getOwnPropertySymbols(methods).forEach(keyCopier); + return ctor; + } + + Iterable.Iterator = Iterator; + + mixin(Iterable, { + + // ### Conversion to other types + + toArray: function() { + assertNotInfinite(this.size); + var array = new Array(this.size || 0); + this.valueSeq().__iterate(function(v, i) { array[i] = v; }); + return array; + }, + + toIndexedSeq: function() { + return new ToIndexedSequence(this); + }, + + toJS: function() { + return this.toSeq().map( + function(value ) {return value && typeof value.toJS === 'function' ? value.toJS() : value} + ).__toJS(); + }, + + toJSON: function() { + return this.toSeq().map( + function(value ) {return value && typeof value.toJSON === 'function' ? value.toJSON() : value} + ).__toJS(); + }, + + toKeyedSeq: function() { + return new ToKeyedSequence(this, true); + }, + + toMap: function() { + // Use Late Binding here to solve the circular dependency. + return Map(this.toKeyedSeq()); + }, + + toObject: function() { + assertNotInfinite(this.size); + var object = {}; + this.__iterate(function(v, k) { object[k] = v; }); + return object; + }, + + toOrderedMap: function() { + // Use Late Binding here to solve the circular dependency. + return OrderedMap(this.toKeyedSeq()); + }, + + toOrderedSet: function() { + // Use Late Binding here to solve the circular dependency. + return OrderedSet(isKeyed(this) ? this.valueSeq() : this); + }, + + toSet: function() { + // Use Late Binding here to solve the circular dependency. + return Set(isKeyed(this) ? this.valueSeq() : this); + }, + + toSetSeq: function() { + return new ToSetSequence(this); + }, + + toSeq: function() { + return isIndexed(this) ? this.toIndexedSeq() : + isKeyed(this) ? this.toKeyedSeq() : + this.toSetSeq(); + }, + + toStack: function() { + // Use Late Binding here to solve the circular dependency. + return Stack(isKeyed(this) ? this.valueSeq() : this); + }, + + toList: function() { + // Use Late Binding here to solve the circular dependency. + return List(isKeyed(this) ? this.valueSeq() : this); + }, + + + // ### Common JavaScript methods and properties + + toString: function() { + return '[Iterable]'; + }, + + __toString: function(head, tail) { + if (this.size === 0) { + return head + tail; + } + return head + ' ' + this.toSeq().map(this.__toStringMapper).join(', ') + ' ' + tail; + }, + + + // ### ES6 Collection methods (ES6 Array and Map) + + concat: function() {var values = SLICE$0.call(arguments, 0); + return reify(this, concatFactory(this, values)); + }, + + includes: function(searchValue) { + return this.some(function(value ) {return is(value, searchValue)}); + }, + + entries: function() { + return this.__iterator(ITERATE_ENTRIES); + }, + + every: function(predicate, context) { + assertNotInfinite(this.size); + var returnValue = true; + this.__iterate(function(v, k, c) { + if (!predicate.call(context, v, k, c)) { + returnValue = false; + return false; + } + }); + return returnValue; + }, + + filter: function(predicate, context) { + return reify(this, filterFactory(this, predicate, context, true)); + }, + + find: function(predicate, context, notSetValue) { + var entry = this.findEntry(predicate, context); + return entry ? entry[1] : notSetValue; + }, + + forEach: function(sideEffect, context) { + assertNotInfinite(this.size); + return this.__iterate(context ? sideEffect.bind(context) : sideEffect); + }, + + join: function(separator) { + assertNotInfinite(this.size); + separator = separator !== undefined ? '' + separator : ','; + var joined = ''; + var isFirst = true; + this.__iterate(function(v ) { + isFirst ? (isFirst = false) : (joined += separator); + joined += v !== null && v !== undefined ? v.toString() : ''; + }); + return joined; + }, + + keys: function() { + return this.__iterator(ITERATE_KEYS); + }, + + map: function(mapper, context) { + return reify(this, mapFactory(this, mapper, context)); + }, + + reduce: function(reducer, initialReduction, context) { + assertNotInfinite(this.size); + var reduction; + var useFirst; + if (arguments.length < 2) { + useFirst = true; + } else { + reduction = initialReduction; + } + this.__iterate(function(v, k, c) { + if (useFirst) { + useFirst = false; + reduction = v; + } else { + reduction = reducer.call(context, reduction, v, k, c); + } + }); + return reduction; + }, + + reduceRight: function(reducer, initialReduction, context) { + var reversed = this.toKeyedSeq().reverse(); + return reversed.reduce.apply(reversed, arguments); + }, + + reverse: function() { + return reify(this, reverseFactory(this, true)); + }, + + slice: function(begin, end) { + return reify(this, sliceFactory(this, begin, end, true)); + }, + + some: function(predicate, context) { + return !this.every(not(predicate), context); + }, + + sort: function(comparator) { + return reify(this, sortFactory(this, comparator)); + }, + + values: function() { + return this.__iterator(ITERATE_VALUES); + }, + + + // ### More sequential methods + + butLast: function() { + return this.slice(0, -1); + }, + + isEmpty: function() { + return this.size !== undefined ? this.size === 0 : !this.some(function() {return true}); + }, + + count: function(predicate, context) { + return ensureSize( + predicate ? this.toSeq().filter(predicate, context) : this + ); + }, + + countBy: function(grouper, context) { + return countByFactory(this, grouper, context); + }, + + equals: function(other) { + return deepEqual(this, other); + }, + + entrySeq: function() { + var iterable = this; + if (iterable._cache) { + // We cache as an entries array, so we can just return the cache! + return new ArraySeq(iterable._cache); + } + var entriesSequence = iterable.toSeq().map(entryMapper).toIndexedSeq(); + entriesSequence.fromEntrySeq = function() {return iterable.toSeq()}; + return entriesSequence; + }, + + filterNot: function(predicate, context) { + return this.filter(not(predicate), context); + }, + + findEntry: function(predicate, context, notSetValue) { + var found = notSetValue; + this.__iterate(function(v, k, c) { + if (predicate.call(context, v, k, c)) { + found = [k, v]; + return false; + } + }); + return found; + }, + + findKey: function(predicate, context) { + var entry = this.findEntry(predicate, context); + return entry && entry[0]; + }, + + findLast: function(predicate, context, notSetValue) { + return this.toKeyedSeq().reverse().find(predicate, context, notSetValue); + }, + + findLastEntry: function(predicate, context, notSetValue) { + return this.toKeyedSeq().reverse().findEntry(predicate, context, notSetValue); + }, + + findLastKey: function(predicate, context) { + return this.toKeyedSeq().reverse().findKey(predicate, context); + }, + + first: function() { + return this.find(returnTrue); + }, + + flatMap: function(mapper, context) { + return reify(this, flatMapFactory(this, mapper, context)); + }, + + flatten: function(depth) { + return reify(this, flattenFactory(this, depth, true)); + }, + + fromEntrySeq: function() { + return new FromEntriesSequence(this); + }, + + get: function(searchKey, notSetValue) { + return this.find(function(_, key) {return is(key, searchKey)}, undefined, notSetValue); + }, + + getIn: function(searchKeyPath, notSetValue) { + var nested = this; + // Note: in an ES6 environment, we would prefer: + // for (var key of searchKeyPath) { + var iter = forceIterator(searchKeyPath); + var step; + while (!(step = iter.next()).done) { + var key = step.value; + nested = nested && nested.get ? nested.get(key, NOT_SET) : NOT_SET; + if (nested === NOT_SET) { + return notSetValue; + } + } + return nested; + }, + + groupBy: function(grouper, context) { + return groupByFactory(this, grouper, context); + }, + + has: function(searchKey) { + return this.get(searchKey, NOT_SET) !== NOT_SET; + }, + + hasIn: function(searchKeyPath) { + return this.getIn(searchKeyPath, NOT_SET) !== NOT_SET; + }, + + isSubset: function(iter) { + iter = typeof iter.includes === 'function' ? iter : Iterable(iter); + return this.every(function(value ) {return iter.includes(value)}); + }, + + isSuperset: function(iter) { + iter = typeof iter.isSubset === 'function' ? iter : Iterable(iter); + return iter.isSubset(this); + }, + + keyOf: function(searchValue) { + return this.findKey(function(value ) {return is(value, searchValue)}); + }, + + keySeq: function() { + return this.toSeq().map(keyMapper).toIndexedSeq(); + }, + + last: function() { + return this.toSeq().reverse().first(); + }, + + lastKeyOf: function(searchValue) { + return this.toKeyedSeq().reverse().keyOf(searchValue); + }, + + max: function(comparator) { + return maxFactory(this, comparator); + }, + + maxBy: function(mapper, comparator) { + return maxFactory(this, comparator, mapper); + }, + + min: function(comparator) { + return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator); + }, + + minBy: function(mapper, comparator) { + return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator, mapper); + }, + + rest: function() { + return this.slice(1); + }, + + skip: function(amount) { + return this.slice(Math.max(0, amount)); + }, + + skipLast: function(amount) { + return reify(this, this.toSeq().reverse().skip(amount).reverse()); + }, + + skipWhile: function(predicate, context) { + return reify(this, skipWhileFactory(this, predicate, context, true)); + }, + + skipUntil: function(predicate, context) { + return this.skipWhile(not(predicate), context); + }, + + sortBy: function(mapper, comparator) { + return reify(this, sortFactory(this, comparator, mapper)); + }, + + take: function(amount) { + return this.slice(0, Math.max(0, amount)); + }, + + takeLast: function(amount) { + return reify(this, this.toSeq().reverse().take(amount).reverse()); + }, + + takeWhile: function(predicate, context) { + return reify(this, takeWhileFactory(this, predicate, context)); + }, + + takeUntil: function(predicate, context) { + return this.takeWhile(not(predicate), context); + }, + + valueSeq: function() { + return this.toIndexedSeq(); + }, + + + // ### Hashable Object + + hashCode: function() { + return this.__hash || (this.__hash = hashIterable(this)); + } + + + // ### Internal + + // abstract __iterate(fn, reverse) + + // abstract __iterator(type, reverse) + }); + + // var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@'; + // var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@'; + // var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@'; + // var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@'; + + var IterablePrototype = Iterable.prototype; + IterablePrototype[IS_ITERABLE_SENTINEL] = true; + IterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.values; + IterablePrototype.__toJS = IterablePrototype.toArray; + IterablePrototype.__toStringMapper = quoteString; + IterablePrototype.inspect = + IterablePrototype.toSource = function() { return this.toString(); }; + IterablePrototype.chain = IterablePrototype.flatMap; + IterablePrototype.contains = IterablePrototype.includes; + + mixin(KeyedIterable, { + + // ### More sequential methods + + flip: function() { + return reify(this, flipFactory(this)); + }, + + mapEntries: function(mapper, context) {var this$0 = this; + var iterations = 0; + return reify(this, + this.toSeq().map( + function(v, k) {return mapper.call(context, [k, v], iterations++, this$0)} + ).fromEntrySeq() + ); + }, + + mapKeys: function(mapper, context) {var this$0 = this; + return reify(this, + this.toSeq().flip().map( + function(k, v) {return mapper.call(context, k, v, this$0)} + ).flip() + ); + } + + }); + + var KeyedIterablePrototype = KeyedIterable.prototype; + KeyedIterablePrototype[IS_KEYED_SENTINEL] = true; + KeyedIterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.entries; + KeyedIterablePrototype.__toJS = IterablePrototype.toObject; + KeyedIterablePrototype.__toStringMapper = function(v, k) {return JSON.stringify(k) + ': ' + quoteString(v)}; + + + + mixin(IndexedIterable, { + + // ### Conversion to other types + + toKeyedSeq: function() { + return new ToKeyedSequence(this, false); + }, + + + // ### ES6 Collection methods (ES6 Array and Map) + + filter: function(predicate, context) { + return reify(this, filterFactory(this, predicate, context, false)); + }, + + findIndex: function(predicate, context) { + var entry = this.findEntry(predicate, context); + return entry ? entry[0] : -1; + }, + + indexOf: function(searchValue) { + var key = this.keyOf(searchValue); + return key === undefined ? -1 : key; + }, + + lastIndexOf: function(searchValue) { + var key = this.lastKeyOf(searchValue); + return key === undefined ? -1 : key; + }, + + reverse: function() { + return reify(this, reverseFactory(this, false)); + }, + + slice: function(begin, end) { + return reify(this, sliceFactory(this, begin, end, false)); + }, + + splice: function(index, removeNum /*, ...values*/) { + var numArgs = arguments.length; + removeNum = Math.max(removeNum | 0, 0); + if (numArgs === 0 || (numArgs === 2 && !removeNum)) { + return this; + } + // If index is negative, it should resolve relative to the size of the + // collection. However size may be expensive to compute if not cached, so + // only call count() if the number is in fact negative. + index = resolveBegin(index, index < 0 ? this.count() : this.size); + var spliced = this.slice(0, index); + return reify( + this, + numArgs === 1 ? + spliced : + spliced.concat(arrCopy(arguments, 2), this.slice(index + removeNum)) + ); + }, + + + // ### More collection methods + + findLastIndex: function(predicate, context) { + var entry = this.findLastEntry(predicate, context); + return entry ? entry[0] : -1; + }, + + first: function() { + return this.get(0); + }, + + flatten: function(depth) { + return reify(this, flattenFactory(this, depth, false)); + }, + + get: function(index, notSetValue) { + index = wrapIndex(this, index); + return (index < 0 || (this.size === Infinity || + (this.size !== undefined && index > this.size))) ? + notSetValue : + this.find(function(_, key) {return key === index}, undefined, notSetValue); + }, + + has: function(index) { + index = wrapIndex(this, index); + return index >= 0 && (this.size !== undefined ? + this.size === Infinity || index < this.size : + this.indexOf(index) !== -1 + ); + }, + + interpose: function(separator) { + return reify(this, interposeFactory(this, separator)); + }, + + interleave: function(/*...iterables*/) { + var iterables = [this].concat(arrCopy(arguments)); + var zipped = zipWithFactory(this.toSeq(), IndexedSeq.of, iterables); + var interleaved = zipped.flatten(true); + if (zipped.size) { + interleaved.size = zipped.size * iterables.length; + } + return reify(this, interleaved); + }, + + keySeq: function() { + return Range(0, this.size); + }, + + last: function() { + return this.get(-1); + }, + + skipWhile: function(predicate, context) { + return reify(this, skipWhileFactory(this, predicate, context, false)); + }, + + zip: function(/*, ...iterables */) { + var iterables = [this].concat(arrCopy(arguments)); + return reify(this, zipWithFactory(this, defaultZipper, iterables)); + }, + + zipWith: function(zipper/*, ...iterables */) { + var iterables = arrCopy(arguments); + iterables[0] = this; + return reify(this, zipWithFactory(this, zipper, iterables)); + } + + }); + + IndexedIterable.prototype[IS_INDEXED_SENTINEL] = true; + IndexedIterable.prototype[IS_ORDERED_SENTINEL] = true; + + + + mixin(SetIterable, { + + // ### ES6 Collection methods (ES6 Array and Map) + + get: function(value, notSetValue) { + return this.has(value) ? value : notSetValue; + }, + + includes: function(value) { + return this.has(value); + }, + + + // ### More sequential methods + + keySeq: function() { + return this.valueSeq(); + } + + }); + + SetIterable.prototype.has = IterablePrototype.includes; + SetIterable.prototype.contains = SetIterable.prototype.includes; + + + // Mixin subclasses + + mixin(KeyedSeq, KeyedIterable.prototype); + mixin(IndexedSeq, IndexedIterable.prototype); + mixin(SetSeq, SetIterable.prototype); + + mixin(KeyedCollection, KeyedIterable.prototype); + mixin(IndexedCollection, IndexedIterable.prototype); + mixin(SetCollection, SetIterable.prototype); + + + // #pragma Helper functions + + function keyMapper(v, k) { + return k; + } + + function entryMapper(v, k) { + return [k, v]; + } + + function not(predicate) { + return function() { + return !predicate.apply(this, arguments); + } + } + + function neg(predicate) { + return function() { + return -predicate.apply(this, arguments); + } + } + + function quoteString(value) { + return typeof value === 'string' ? JSON.stringify(value) : String(value); + } + + function defaultZipper() { + return arrCopy(arguments); + } + + function defaultNegComparator(a, b) { + return a < b ? 1 : a > b ? -1 : 0; + } + + function hashIterable(iterable) { + if (iterable.size === Infinity) { + return 0; + } + var ordered = isOrdered(iterable); + var keyed = isKeyed(iterable); + var h = ordered ? 1 : 0; + var size = iterable.__iterate( + keyed ? + ordered ? + function(v, k) { h = 31 * h + hashMerge(hash(v), hash(k)) | 0; } : + function(v, k) { h = h + hashMerge(hash(v), hash(k)) | 0; } : + ordered ? + function(v ) { h = 31 * h + hash(v) | 0; } : + function(v ) { h = h + hash(v) | 0; } + ); + return murmurHashOfSize(size, h); + } + + function murmurHashOfSize(size, h) { + h = imul(h, 0xCC9E2D51); + h = imul(h << 15 | h >>> -15, 0x1B873593); + h = imul(h << 13 | h >>> -13, 5); + h = (h + 0xE6546B64 | 0) ^ size; + h = imul(h ^ h >>> 16, 0x85EBCA6B); + h = imul(h ^ h >>> 13, 0xC2B2AE35); + h = smi(h ^ h >>> 16); + return h; + } + + function hashMerge(a, b) { + return a ^ b + 0x9E3779B9 + (a << 6) + (a >> 2) | 0; // int + } + + var Immutable = { + + Iterable: Iterable, + + Seq: Seq, + Collection: Collection, + Map: Map, + OrderedMap: OrderedMap, + List: List, + Stack: Stack, + Set: Set, + OrderedSet: OrderedSet, + + Record: Record, + Range: Range, + Repeat: Repeat, + + is: is, + fromJS: fromJS + + }; + + return Immutable; + +})); diff --git a/backend/static/drf-yasg/immutable.js.gz b/backend/static/drf-yasg/immutable.js.gz new file mode 100644 index 0000000..5d1a29b Binary files /dev/null and b/backend/static/drf-yasg/immutable.js.gz differ diff --git a/backend/static/drf-yasg/immutable.min.js b/backend/static/drf-yasg/immutable.min.js new file mode 100644 index 0000000..5ea82ee --- /dev/null +++ b/backend/static/drf-yasg/immutable.min.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.Immutable=e()}(this,function(){"use strict";function t(t,e){e&&(t.prototype=Object.create(e.prototype)),t.prototype.constructor=t}function e(t){return o(t)?t:O(t)}function r(t){return u(t)?t:x(t)}function n(t){return s(t)?t:k(t)}function i(t){return o(t)&&!a(t)?t:A(t)}function o(t){return!(!t||!t[ar])}function u(t){return!(!t||!t[hr])}function s(t){return!(!t||!t[fr])}function a(t){return u(t)||s(t)}function h(t){return!(!t||!t[cr])}function f(t){return t.value=!1,t}function c(t){t&&(t.value=!0)}function _(){}function p(t,e){e=e||0;for(var r=Math.max(0,t.length-e),n=Array(r),i=0;r>i;i++)n[i]=t[i+e];return n}function v(t){return void 0===t.size&&(t.size=t.__iterate(y)),t.size}function l(t,e){if("number"!=typeof e){var r=e>>>0;if(""+r!==e||4294967295===r)return NaN;e=r}return 0>e?v(t)+e:e}function y(){return!0}function d(t,e,r){return(0===t||void 0!==r&&-r>=t)&&(void 0===e||void 0!==r&&e>=r)}function m(t,e){return w(t,e,0)}function g(t,e){return w(t,e,e)}function w(t,e,r){return void 0===t?r:0>t?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function S(t){this.next=t}function z(t,e,r,n){var i=0===t?e:1===t?r:[e,r];return n?n.value=i:n={value:i,done:!1},n}function I(){return{value:void 0,done:!0}}function b(t){return!!M(t)}function q(t){return t&&"function"==typeof t.next}function D(t){var e=M(t);return e&&e.call(t)}function M(t){var e=t&&(zr&&t[zr]||t[Ir]);return"function"==typeof e?e:void 0}function E(t){return t&&"number"==typeof t.length}function O(t){return null===t||void 0===t?T():o(t)?t.toSeq():C(t)}function x(t){return null===t||void 0===t?T().toKeyedSeq():o(t)?u(t)?t.toSeq():t.fromEntrySeq():B(t)}function k(t){return null===t||void 0===t?T():o(t)?u(t)?t.entrySeq():t.toIndexedSeq():W(t)}function A(t){return(null===t||void 0===t?T():o(t)?u(t)?t.entrySeq():t:W(t)).toSetSeq()}function j(t){this._array=t,this.size=t.length}function R(t){var e=Object.keys(t);this._object=t,this._keys=e, +this.size=e.length}function U(t){this._iterable=t,this.size=t.length||t.size}function K(t){this._iterator=t,this._iteratorCache=[]}function L(t){return!(!t||!t[qr])}function T(){return Dr||(Dr=new j([]))}function B(t){var e=Array.isArray(t)?new j(t).fromEntrySeq():q(t)?new K(t).fromEntrySeq():b(t)?new U(t).fromEntrySeq():"object"==typeof t?new R(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function W(t){var e=J(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function C(t){var e=J(t)||"object"==typeof t&&new R(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function J(t){return E(t)?new j(t):q(t)?new K(t):b(t)?new U(t):void 0}function N(t,e,r,n){var i=t._cache;if(i){for(var o=i.length-1,u=0;o>=u;u++){var s=i[r?o-u:u];if(e(s[1],n?s[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,r)}function P(t,e,r,n){var i=t._cache;if(i){var o=i.length-1,u=0;return new S(function(){var t=i[r?o-u:u];return u++>o?I():z(e,n?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,r)}function H(t,e){return e?V(e,t,"",{"":t}):Y(t)}function V(t,e,r,n){return Array.isArray(e)?t.call(n,r,k(e).map(function(r,n){return V(t,r,n,e)})):Q(e)?t.call(n,r,x(e).map(function(r,n){return V(t,r,n,e)})):e}function Y(t){return Array.isArray(t)?k(t).map(Y).toList():Q(t)?x(t).map(Y).toMap():t}function Q(t){return t&&(t.constructor===Object||void 0===t.constructor)}function X(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return"function"==typeof t.equals&&"function"==typeof e.equals&&t.equals(e)?!0:!1}function F(t,e){if(t===e)return!0;if(!o(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||u(t)!==u(e)||s(t)!==s(e)||h(t)!==h(e))return!1;if(0===t.size&&0===e.size)return!0; +var r=!a(t);if(h(t)){var n=t.entries();return e.every(function(t,e){var i=n.next().value;return i&&X(i[1],t)&&(r||X(i[0],e))})&&n.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var f=t;t=e,e=f}var c=!0,_=e.__iterate(function(e,n){return(r?t.has(e):i?X(e,t.get(n,yr)):X(t.get(n,yr),e))?void 0:(c=!1,!1)});return c&&t.size===_}function G(t,e){if(!(this instanceof G))return new G(t,e);if(this._value=t,this.size=void 0===e?1/0:Math.max(0,e),0===this.size){if(Mr)return Mr;Mr=this}}function Z(t,e){if(!t)throw Error(e)}function $(t,e,r){if(!(this instanceof $))return new $(t,e,r);if(Z(0!==r,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),r=void 0===r?1:Math.abs(r),t>e&&(r=-r),this._start=t,this._end=e,this._step=r,this.size=Math.max(0,Math.ceil((e-t)/r-1)+1),0===this.size){if(Er)return Er;Er=this}}function tt(){throw TypeError("Abstract")}function et(){}function rt(){}function nt(){}function it(t){return t>>>1&1073741824|3221225471&t}function ot(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){if(t!==t||t===1/0)return 0;var r=0|t;for(r!==t&&(r^=4294967295*t);t>4294967295;)t/=4294967295,r^=t;return it(r)}if("string"===e)return t.length>Kr?ut(t):st(t);if("function"==typeof t.hashCode)return t.hashCode();if("object"===e)return at(t);if("function"==typeof t.toString)return st(""+t);throw Error("Value type "+e+" cannot be hashed.")}function ut(t){var e=Br[t];return void 0===e&&(e=st(t),Tr===Lr&&(Tr=0,Br={}),Tr++,Br[t]=e),e}function st(t){for(var e=0,r=0;t.length>r;r++)e=31*e+t.charCodeAt(r)|0;return it(e)}function at(t){var e;if(jr&&(e=Or.get(t),void 0!==e))return e;if(e=t[Ur],void 0!==e)return e;if(!Ar){if(e=t.propertyIsEnumerable&&t.propertyIsEnumerable[Ur],void 0!==e)return e;if(e=ht(t),void 0!==e)return e}if(e=++Rr,1073741824&Rr&&(Rr=0),jr)Or.set(t,e);else{if(void 0!==kr&&kr(t)===!1)throw Error("Non-extensible objects are not allowed as keys."); +if(Ar)Object.defineProperty(t,Ur,{enumerable:!1,configurable:!1,writable:!1,value:e});else if(void 0!==t.propertyIsEnumerable&&t.propertyIsEnumerable===t.constructor.prototype.propertyIsEnumerable)t.propertyIsEnumerable=function(){return this.constructor.prototype.propertyIsEnumerable.apply(this,arguments)},t.propertyIsEnumerable[Ur]=e;else{if(void 0===t.nodeType)throw Error("Unable to set a non-enumerable property on object.");t[Ur]=e}}return e}function ht(t){if(t&&t.nodeType>0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ft(t){Z(t!==1/0,"Cannot perform this action with an infinite size.")}function ct(t){return null===t||void 0===t?zt():_t(t)&&!h(t)?t:zt().withMutations(function(e){var n=r(t);ft(n.size),n.forEach(function(t,r){return e.set(r,t)})})}function _t(t){return!(!t||!t[Wr])}function pt(t,e){this.ownerID=t,this.entries=e}function vt(t,e,r){this.ownerID=t,this.bitmap=e,this.nodes=r}function lt(t,e,r){this.ownerID=t,this.count=e,this.nodes=r}function yt(t,e,r){this.ownerID=t,this.keyHash=e,this.entries=r}function dt(t,e,r){this.ownerID=t,this.keyHash=e,this.entry=r}function mt(t,e,r){this._type=e,this._reverse=r,this._stack=t._root&&wt(t._root)}function gt(t,e){return z(t,e[0],e[1])}function wt(t,e){return{node:t,index:0,__prev:e}}function St(t,e,r,n){var i=Object.create(Cr);return i.size=t,i._root=e,i.__ownerID=r,i.__hash=n,i.__altered=!1,i}function zt(){return Jr||(Jr=St(0))}function It(t,e,r){var n,i;if(t._root){var o=f(dr),u=f(mr);if(n=bt(t._root,t.__ownerID,0,void 0,e,r,o,u),!u.value)return t;i=t.size+(o.value?r===yr?-1:1:0)}else{if(r===yr)return t;i=1,n=new pt(t.__ownerID,[[e,r]])}return t.__ownerID?(t.size=i,t._root=n,t.__hash=void 0,t.__altered=!0,t):n?St(i,n):zt()}function bt(t,e,r,n,i,o,u,s){return t?t.update(e,r,n,i,o,u,s):o===yr?t:(c(s),c(u),new dt(e,n,[i,o]))}function qt(t){return t.constructor===dt||t.constructor===yt}function Dt(t,e,r,n,i){if(t.keyHash===n)return new yt(e,n,[t.entry,i]);var o,u=(0===r?t.keyHash:t.keyHash>>>r)&lr,s=(0===r?n:n>>>r)&lr,a=u===s?[Dt(t,e,r+pr,n,i)]:(o=new dt(e,n,i), +s>u?[t,o]:[o,t]);return new vt(e,1<o;o++){var u=e[o];i=i.update(t,0,void 0,u[0],u[1])}return i}function Et(t,e,r,n){for(var i=0,o=0,u=Array(r),s=0,a=1,h=e.length;h>s;s++,a<<=1){var f=e[s];void 0!==f&&s!==n&&(i|=a,u[o++]=f)}return new vt(t,i,u)}function Ot(t,e,r,n,i){for(var o=0,u=Array(vr),s=0;0!==r;s++,r>>>=1)u[s]=1&r?e[o++]:void 0;return u[n]=i,new lt(t,o+1,u)}function xt(t,e,n){for(var i=[],u=0;n.length>u;u++){var s=n[u],a=r(s);o(s)||(a=a.map(function(t){return H(t)})),i.push(a)}return jt(t,e,i)}function kt(t,e,r){return t&&t.mergeDeep&&o(e)?t.mergeDeep(e):X(t,e)?t:e}function At(t){return function(e,r,n){if(e&&e.mergeDeepWith&&o(r))return e.mergeDeepWith(t,r);var i=t(e,r,n);return X(e,i)?e:i}}function jt(t,e,r){return r=r.filter(function(t){return 0!==t.size}),0===r.length?t:0!==t.size||t.__ownerID||1!==r.length?t.withMutations(function(t){for(var n=e?function(r,n){t.update(n,yr,function(t){return t===yr?r:e(t,r,n)})}:function(e,r){t.set(r,e)},i=0;r.length>i;i++)r[i].forEach(n)}):t.constructor(r[0])}function Rt(t,e,r,n){var i=t===yr,o=e.next();if(o.done){var u=i?r:t,s=n(u);return s===u?t:s}Z(i||t&&t.set,"invalid keyPath");var a=o.value,h=i?yr:t.get(a,yr),f=Rt(h,e,r,n);return f===h?t:f===yr?t.remove(a):(i?zt():t).set(a,f)}function Ut(t){return t-=t>>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function Kt(t,e,r,n){var i=n?t:p(t);return i[e]=r,i}function Lt(t,e,r,n){var i=t.length+1;if(n&&e+1===i)return t[e]=r,t;for(var o=Array(i),u=0,s=0;i>s;s++)s===e?(o[s]=r,u=-1):o[s]=t[s+u];return o}function Tt(t,e,r){var n=t.length-1;if(r&&e===n)return t.pop(),t;for(var i=Array(n),o=0,u=0;n>u;u++)u===e&&(o=1),i[u]=t[u+o];return i}function Bt(t){var e=Pt();if(null===t||void 0===t)return e;if(Wt(t))return t;var r=n(t),i=r.size;return 0===i?e:(ft(i),i>0&&vr>i?Nt(0,i,pr,null,new Ct(r.toArray())):e.withMutations(function(t){t.setSize(i),r.forEach(function(e,r){return t.set(r,e)})}))}function Wt(t){ +return!(!t||!t[Vr])}function Ct(t,e){this.array=t,this.ownerID=e}function Jt(t,e){function r(t,e,r){return 0===e?n(t,r):i(t,e,r)}function n(t,r){var n=r===s?a&&a.array:t&&t.array,i=r>o?0:o-r,h=u-r;return h>vr&&(h=vr),function(){if(i===h)return Xr;var t=e?--h:i++;return n&&n[t]}}function i(t,n,i){var s,a=t&&t.array,h=i>o?0:o-i>>n,f=(u-i>>n)+1;return f>vr&&(f=vr),function(){for(;;){if(s){var t=s();if(t!==Xr)return t;s=null}if(h===f)return Xr;var o=e?--f:h++;s=r(a&&a[o],n-pr,i+(o<=t.size||0>e)return t.withMutations(function(t){0>e?Xt(t,e).set(0,r):Xt(t,0,e+1).set(e,r)});e+=t._origin;var n=t._tail,i=t._root,o=f(mr);return e>=Gt(t._capacity)?n=Vt(n,t.__ownerID,0,e,r,o):i=Vt(i,t.__ownerID,t._level,e,r,o),o.value?t.__ownerID?(t._root=i,t._tail=n,t.__hash=void 0,t.__altered=!0,t):Nt(t._origin,t._capacity,t._level,i,n):t}function Vt(t,e,r,n,i,o){var u=n>>>r&lr,s=t&&t.array.length>u;if(!s&&void 0===i)return t;var a;if(r>0){var h=t&&t.array[u],f=Vt(h,e,r-pr,n,i,o);return f===h?t:(a=Yt(t,e),a.array[u]=f,a)}return s&&t.array[u]===i?t:(c(o),a=Yt(t,e),void 0===i&&u===a.array.length-1?a.array.pop():a.array[u]=i,a)}function Yt(t,e){return e&&t&&e===t.ownerID?t:new Ct(t?t.array.slice():[],e)}function Qt(t,e){if(e>=Gt(t._capacity))return t._tail;if(1<e){for(var r=t._root,n=t._level;r&&n>0;)r=r.array[e>>>n&lr],n-=pr;return r}}function Xt(t,e,r){void 0!==e&&(e=0|e),void 0!==r&&(r=0|r);var n=t.__ownerID||new _,i=t._origin,o=t._capacity,u=i+e,s=void 0===r?o:0>r?o+r:i+r;if(u===i&&s===o)return t;if(u>=s)return t.clear();for(var a=t._level,h=t._root,f=0;0>u+f;)h=new Ct(h&&h.array.length?[void 0,h]:[],n),a+=pr,f+=1<=1<p?Qt(t,s-1):p>c?new Ct([],n):v;if(v&&p>c&&o>u&&v.array.length){h=Yt(h,n);for(var y=h,d=a;d>pr;d-=pr){var m=c>>>d&lr;y=y.array[m]=Yt(y.array[m],n)}y.array[c>>>pr&lr]=v}if(o>s&&(l=l&&l.removeAfter(n,0,s)),u>=p)u-=p,s-=p,a=pr,h=null,l=l&&l.removeBefore(n,0,u);else if(u>i||c>p){for(f=0;h;){var g=u>>>a&lr;if(g!==p>>>a&lr)break;g&&(f+=(1<i&&(h=h.removeBefore(n,a,u-f)),h&&c>p&&(h=h.removeAfter(n,a,p-f)),f&&(u-=f,s-=f)}return t.__ownerID?(t.size=s-u,t._origin=u,t._capacity=s,t._level=a,t._root=h,t._tail=l,t.__hash=void 0,t.__altered=!0,t):Nt(u,s,a,h,l)}function Ft(t,e,r){for(var i=[],u=0,s=0;r.length>s;s++){var a=r[s],h=n(a);h.size>u&&(u=h.size),o(a)||(h=h.map(function(t){return H(t)})),i.push(h)}return u>t.size&&(t=t.setSize(u)),jt(t,e,i)}function Gt(t){return vr>t?0:t-1>>>pr<=vr&&u.size>=2*o.size?(i=u.filter(function(t,e){return void 0!==t&&s!==e}),n=i.toKeyedSeq().map(function(t){return t[0]}).flip().toMap(),t.__ownerID&&(n.__ownerID=i.__ownerID=t.__ownerID)):(n=o.remove(e),i=s===u.size-1?u.pop():u.set(s,void 0))}else if(a){if(r===u.get(s)[1])return t;n=o,i=u.set(s,[e,r])}else n=o.set(e,u.size),i=u.set(u.size,[e,r]);return t.__ownerID?(t.size=n.size,t._map=n,t._list=i,t.__hash=void 0,t):te(n,i)}function ne(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ie(t){this._iter=t,this.size=t.size}function oe(t){this._iter=t,this.size=t.size}function ue(t){this._iter=t,this.size=t.size}function se(t){var e=Ee(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this); +return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=Oe,e.__iterateUncached=function(e,r){var n=this;return t.__iterate(function(t,r){return e(r,t,n)!==!1},r)},e.__iteratorUncached=function(e,r){if(e===Sr){var n=t.__iterator(e,r);return new S(function(){var t=n.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===wr?gr:wr,r)},e}function ae(t,e,r){var n=Ee(t);return n.size=t.size,n.has=function(e){return t.has(e)},n.get=function(n,i){var o=t.get(n,yr);return o===yr?i:e.call(r,o,n,t)},n.__iterateUncached=function(n,i){var o=this;return t.__iterate(function(t,i,u){return n(e.call(r,t,i,u),i,o)!==!1},i)},n.__iteratorUncached=function(n,i){var o=t.__iterator(Sr,i);return new S(function(){var i=o.next();if(i.done)return i;var u=i.value,s=u[0];return z(n,s,e.call(r,u[1],s,t),i)})},n}function he(t,e){var r=Ee(t);return r._iter=t,r.size=t.size,r.reverse=function(){return t},t.flip&&(r.flip=function(){var e=se(t);return e.reverse=function(){return t.flip()},e}),r.get=function(r,n){return t.get(e?r:-1-r,n)},r.has=function(r){return t.has(e?r:-1-r)},r.includes=function(e){return t.includes(e)},r.cacheResult=Oe,r.__iterate=function(e,r){var n=this;return t.__iterate(function(t,r){return e(t,r,n)},!r)},r.__iterator=function(e,r){return t.__iterator(e,!r)},r}function fe(t,e,r,n){var i=Ee(t);return n&&(i.has=function(n){var i=t.get(n,yr);return i!==yr&&!!e.call(r,i,n,t)},i.get=function(n,i){var o=t.get(n,yr);return o!==yr&&e.call(r,o,n,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,s=0;return t.__iterate(function(t,o,a){return e.call(r,t,o,a)?(s++,i(t,n?o:s-1,u)):void 0},o),s},i.__iteratorUncached=function(i,o){var u=t.__iterator(Sr,o),s=0;return new S(function(){for(;;){var o=u.next();if(o.done)return o;var a=o.value,h=a[0],f=a[1];if(e.call(r,f,h,t))return z(i,n?h:s++,f,o)}})},i}function ce(t,e,r){var n=ct().asMutable();return t.__iterate(function(i,o){n.update(e.call(r,i,o,t),0,function(t){ +return t+1})}),n.asImmutable()}function _e(t,e,r){var n=u(t),i=(h(t)?Zt():ct()).asMutable();t.__iterate(function(o,u){i.update(e.call(r,o,u,t),function(t){return t=t||[],t.push(n?[u,o]:o),t})});var o=Me(t);return i.map(function(e){return be(t,o(e))})}function pe(t,e,r,n){var i=t.size;if(void 0!==e&&(e=0|e),void 0!==r&&(r=r===1/0?i:0|r),d(e,r,i))return t;var o=m(e,i),u=g(r,i);if(o!==o||u!==u)return pe(t.toSeq().cacheResult(),e,r,n);var s,a=u-o;a===a&&(s=0>a?0:a);var h=Ee(t);return h.size=0===s?s:t.size&&s||void 0,!n&&L(t)&&s>=0&&(h.get=function(e,r){return e=l(this,e),e>=0&&s>e?t.get(e+o,r):r}),h.__iterateUncached=function(e,r){var i=this;if(0===s)return 0;if(r)return this.cacheResult().__iterate(e,r);var u=0,a=!0,h=0;return t.__iterate(function(t,r){return a&&(a=u++s)return I();var t=i.next();return n||e===wr?t:e===gr?z(e,a-1,void 0,t):z(e,a-1,t.value[1],t)})},h}function ve(t,e,r){var n=Ee(t);return n.__iterateUncached=function(n,i){var o=this;if(i)return this.cacheResult().__iterate(n,i);var u=0;return t.__iterate(function(t,i,s){return e.call(r,t,i,s)&&++u&&n(t,i,o)}),u},n.__iteratorUncached=function(n,i){var o=this;if(i)return this.cacheResult().__iterator(n,i);var u=t.__iterator(Sr,i),s=!0;return new S(function(){if(!s)return I();var t=u.next();if(t.done)return t;var i=t.value,a=i[0],h=i[1];return e.call(r,h,a,o)?n===Sr?t:z(n,a,h,t):(s=!1,I())})},n}function le(t,e,r,n){var i=Ee(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var s=!0,a=0;return t.__iterate(function(t,o,h){return s&&(s=e.call(r,t,o,h))?void 0:(a++,i(t,n?o:a-1,u))}),a},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var s=t.__iterator(Sr,o),a=!0,h=0;return new S(function(){var t,o,f;do{if(t=s.next(),t.done)return n||i===wr?t:i===gr?z(i,h++,void 0,t):z(i,h++,t.value[1],t); +var c=t.value;o=c[0],f=c[1],a&&(a=e.call(r,f,o,u))}while(a);return i===Sr?t:z(i,o,f,t)})},i}function ye(t,e){var n=u(t),i=[t].concat(e).map(function(t){return o(t)?n&&(t=r(t)):t=n?B(t):W(Array.isArray(t)?t:[t]),t}).filter(function(t){return 0!==t.size});if(0===i.length)return t;if(1===i.length){var a=i[0];if(a===t||n&&u(a)||s(t)&&s(a))return a}var h=new j(i);return n?h=h.toKeyedSeq():s(t)||(h=h.toSetSeq()),h=h.flatten(!0),h.size=i.reduce(function(t,e){if(void 0!==t){var r=e.size;if(void 0!==r)return t+r}},0),h}function de(t,e,r){var n=Ee(t);return n.__iterateUncached=function(n,i){function u(t,h){var f=this;t.__iterate(function(t,i){return(!e||e>h)&&o(t)?u(t,h+1):n(t,r?i:s++,f)===!1&&(a=!0),!a},i)}var s=0,a=!1;return u(t,0),s},n.__iteratorUncached=function(n,i){var u=t.__iterator(n,i),s=[],a=0;return new S(function(){for(;u;){var t=u.next();if(t.done===!1){var h=t.value;if(n===Sr&&(h=h[1]),e&&!(e>s.length)||!o(h))return r?t:z(n,a++,h,t);s.push(u),u=h.__iterator(n,i)}else u=s.pop()}return I()})},n}function me(t,e,r){var n=Me(t);return t.toSeq().map(function(i,o){return n(e.call(r,i,o,t))}).flatten(!0)}function ge(t,e){var r=Ee(t);return r.size=t.size&&2*t.size-1,r.__iterateUncached=function(r,n){var i=this,o=0;return t.__iterate(function(t,n){return(!o||r(e,o++,i)!==!1)&&r(t,o++,i)!==!1},n),o},r.__iteratorUncached=function(r,n){var i,o=t.__iterator(wr,n),u=0;return new S(function(){return(!i||u%2)&&(i=o.next(),i.done)?i:u%2?z(r,u++,e):z(r,u++,i.value,i)})},r}function we(t,e,r){e||(e=xe);var n=u(t),i=0,o=t.toSeq().map(function(e,n){return[n,e,i++,r?r(e,n,t):e]}).toArray();return o.sort(function(t,r){return e(t[3],r[3])||t[2]-r[2]}).forEach(n?function(t,e){o[e].length=2}:function(t,e){o[e]=t[1]}),n?x(o):s(t)?k(o):A(o)}function Se(t,e,r){if(e||(e=xe),r){var n=t.toSeq().map(function(e,n){return[e,r(e,n,t)]}).reduce(function(t,r){return ze(e,t[1],r[1])?r:t});return n&&n[0]}return t.reduce(function(t,r){return ze(e,t,r)?r:t})}function ze(t,e,r){var n=t(r,e);return 0===n&&r!==e&&(void 0===r||null===r||r!==r)||n>0}function Ie(t,r,n){ +var i=Ee(t);return i.size=new j(n).map(function(t){return t.size}).min(),i.__iterate=function(t,e){for(var r,n=this.__iterator(wr,e),i=0;!(r=n.next()).done&&t(r.value,i++,this)!==!1;);return i},i.__iteratorUncached=function(t,i){var o=n.map(function(t){return t=e(t),D(i?t.reverse():t)}),u=0,s=!1;return new S(function(){var e;return s||(e=o.map(function(t){return t.next()}),s=e.some(function(t){return t.done})),s?I():z(t,u++,r.apply(null,e.map(function(t){return t.value})))})},i}function be(t,e){return L(t)?e:t.constructor(e)}function qe(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function De(t){return ft(t.size),v(t)}function Me(t){return u(t)?r:s(t)?n:i}function Ee(t){return Object.create((u(t)?x:s(t)?k:A).prototype)}function Oe(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):O.prototype.cacheResult.call(this)}function xe(t,e){return t>e?1:e>t?-1:0}function ke(t){var r=D(t);if(!r){if(!E(t))throw new TypeError("Expected iterable or array-like: "+t);r=D(e(t))}return r}function Ae(t,e){var r,n=function(o){if(o instanceof n)return o;if(!(this instanceof n))return new n(o);if(!r){r=!0;var u=Object.keys(t);Ue(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=ct(o)},i=n.prototype=Object.create(Gr);return i.constructor=n,n}function je(t,e,r){var n=Object.create(Object.getPrototypeOf(t));return n._map=e,n.__ownerID=r,n}function Re(t){return t._name||t.constructor.name||"Record"}function Ue(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(r){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){Z(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Le(t){return null===t||void 0===t?Ce():Te(t)&&!h(t)?t:Ce().withMutations(function(e){var r=i(t);ft(r.size),r.forEach(function(t){return e.add(t)})})}function Te(t){return!(!t||!t[Zr])}function Be(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function We(t,e){var r=Object.create($r); +return r.size=t?t.size:0,r._map=t,r.__ownerID=e,r}function Ce(){return tn||(tn=We(zt()))}function Je(t){return null===t||void 0===t?He():Ne(t)?t:He().withMutations(function(e){var r=i(t);ft(r.size),r.forEach(function(t){return e.add(t)})})}function Ne(t){return Te(t)&&h(t)}function Pe(t,e){var r=Object.create(en);return r.size=t?t.size:0,r._map=t,r.__ownerID=e,r}function He(){return rn||(rn=Pe(ee()))}function Ve(t){return null===t||void 0===t?Xe():Ye(t)?t:Xe().unshiftAll(t)}function Ye(t){return!(!t||!t[nn])}function Qe(t,e,r,n){var i=Object.create(on);return i.size=t,i._head=e,i.__ownerID=r,i.__hash=n,i.__altered=!1,i}function Xe(){return un||(un=Qe(0))}function Fe(t,e){var r=function(r){t.prototype[r]=e[r]};return Object.keys(e).forEach(r),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(e).forEach(r),t}function Ge(t,e){return e}function Ze(t,e){return[e,t]}function $e(t){return function(){return!t.apply(this,arguments)}}function tr(t){return function(){return-t.apply(this,arguments)}}function er(t){return"string"==typeof t?JSON.stringify(t):t+""}function rr(){return p(arguments)}function nr(t,e){return e>t?1:t>e?-1:0}function ir(t){if(t.size===1/0)return 0;var e=h(t),r=u(t),n=e?1:0,i=t.__iterate(r?e?function(t,e){n=31*n+ur(ot(t),ot(e))|0}:function(t,e){n=n+ur(ot(t),ot(e))|0}:e?function(t){n=31*n+ot(t)|0}:function(t){n=n+ot(t)|0});return or(i,n)}function or(t,e){return e=xr(e,3432918353),e=xr(e<<15|e>>>-15,461845907),e=xr(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=xr(e^e>>>16,2246822507),e=xr(e^e>>>13,3266489909),e=it(e^e>>>16)}function ur(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var sr=Array.prototype.slice;t(r,e),t(n,e),t(i,e),e.isIterable=o,e.isKeyed=u,e.isIndexed=s,e.isAssociative=a,e.isOrdered=h,e.Keyed=r,e.Indexed=n,e.Set=i;var ar="@@__IMMUTABLE_ITERABLE__@@",hr="@@__IMMUTABLE_KEYED__@@",fr="@@__IMMUTABLE_INDEXED__@@",cr="@@__IMMUTABLE_ORDERED__@@",_r="delete",pr=5,vr=1<=i;i++)if(t(r[e?n-i:i],i,this)===!1)return i+1;return i},j.prototype.__iterator=function(t,e){var r=this._array,n=r.length-1,i=0;return new S(function(){return i>n?I():z(t,i,r[e?n-i++:i++])})},t(R,x),R.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},R.prototype.has=function(t){return this._object.hasOwnProperty(t)},R.prototype.__iterate=function(t,e){for(var r=this._object,n=this._keys,i=n.length-1,o=0;i>=o;o++){var u=n[e?i-o:o];if(t(r[u],u,this)===!1)return o+1}return o},R.prototype.__iterator=function(t,e){var r=this._object,n=this._keys,i=n.length-1,o=0;return new S(function(){var u=n[e?i-o:o];return o++>i?I():z(t,u,r[u])})},R.prototype[cr]=!0,t(U,k),U.prototype.__iterateUncached=function(t,e){if(e)return this.cacheResult().__iterate(t,e); +var r=this._iterable,n=D(r),i=0;if(q(n))for(var o;!(o=n.next()).done&&t(o.value,i++,this)!==!1;);return i},U.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var r=this._iterable,n=D(r);if(!q(n))return new S(I);var i=0;return new S(function(){var e=n.next();return e.done?e:z(t,i++,e.value)})},t(K,k),K.prototype.__iterateUncached=function(t,e){if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,n=this._iteratorCache,i=0;n.length>i;)if(t(n[i],i++,this)===!1)return i;for(var o;!(o=r.next()).done;){var u=o.value;if(n[i]=u,t(u,i++,this)===!1)break}return i},K.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var r=this._iterator,n=this._iteratorCache,i=0;return new S(function(){if(i>=n.length){var e=r.next();if(e.done)return e;n[i]=e.value}return z(t,i,n[i++])})};var Dr;t(G,k),G.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},G.prototype.get=function(t,e){return this.has(t)?this._value:e},G.prototype.includes=function(t){return X(this._value,t)},G.prototype.slice=function(t,e){var r=this.size;return d(t,e,r)?this:new G(this._value,g(e,r)-m(t,r))},G.prototype.reverse=function(){return this},G.prototype.indexOf=function(t){return X(this._value,t)?0:-1},G.prototype.lastIndexOf=function(t){return X(this._value,t)?this.size:-1},G.prototype.__iterate=function(t,e){for(var r=0;this.size>r;r++)if(t(this._value,r,this)===!1)return r+1;return r},G.prototype.__iterator=function(t,e){var r=this,n=0;return new S(function(){return r.size>n?z(t,n++,r._value):I()})},G.prototype.equals=function(t){return t instanceof G?X(this._value,t._value):F(t)};var Mr;t($,k),$.prototype.toString=function(){return 0===this.size?"Range []":"Range [ "+this._start+"..."+this._end+(1!==this._step?" by "+this._step:"")+" ]"},$.prototype.get=function(t,e){return this.has(t)?this._start+l(this,t)*this._step:e},$.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&this.size>e&&e===Math.floor(e); +},$.prototype.slice=function(t,e){return d(t,e,this.size)?this:(t=m(t,this.size),e=g(e,this.size),t>=e?new $(0,0):new $(this.get(t,this._end),this.get(e,this._end),this._step))},$.prototype.indexOf=function(t){var e=t-this._start;if(e%this._step===0){var r=e/this._step;if(r>=0&&this.size>r)return r}return-1},$.prototype.lastIndexOf=function(t){return this.indexOf(t)},$.prototype.__iterate=function(t,e){for(var r=this.size-1,n=this._step,i=e?this._start+r*n:this._start,o=0;r>=o;o++){if(t(i,o,this)===!1)return o+1;i+=e?-n:n}return o},$.prototype.__iterator=function(t,e){var r=this.size-1,n=this._step,i=e?this._start+r*n:this._start,o=0;return new S(function(){var u=i;return i+=e?-n:n,o>r?I():z(t,o++,u)})},$.prototype.equals=function(t){return t instanceof $?this._start===t._start&&this._end===t._end&&this._step===t._step:F(this,t)};var Er;t(tt,e),t(et,tt),t(rt,tt),t(nt,tt),tt.Keyed=et,tt.Indexed=rt,tt.Set=nt;var Or,xr="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(t,e){t=0|t,e=0|e;var r=65535&t,n=65535&e;return r*n+((t>>>16)*n+r*(e>>>16)<<16>>>0)|0},kr=Object.isExtensible,Ar=function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}}(),jr="function"==typeof WeakMap;jr&&(Or=new WeakMap);var Rr=0,Ur="__immutablehash__";"function"==typeof Symbol&&(Ur=Symbol(Ur));var Kr=16,Lr=255,Tr=0,Br={};t(ct,et),ct.of=function(){var t=sr.call(arguments,0);return zt().withMutations(function(e){for(var r=0;t.length>r;r+=2){if(r+1>=t.length)throw Error("Missing value for key: "+t[r]);e.set(t[r],t[r+1])}})},ct.prototype.toString=function(){return this.__toString("Map {","}")},ct.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},ct.prototype.set=function(t,e){return It(this,t,e)},ct.prototype.setIn=function(t,e){return this.updateIn(t,yr,function(){return e})},ct.prototype.remove=function(t){return It(this,t,yr)},ct.prototype.deleteIn=function(t){return this.updateIn(t,function(){return yr})},ct.prototype.update=function(t,e,r){return 1===arguments.length?t(this):this.updateIn([t],e,r); +},ct.prototype.updateIn=function(t,e,r){r||(r=e,e=void 0);var n=Rt(this,ke(t),e,r);return n===yr?void 0:n},ct.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):zt()},ct.prototype.merge=function(){return xt(this,void 0,arguments)},ct.prototype.mergeWith=function(t){var e=sr.call(arguments,1);return xt(this,t,e)},ct.prototype.mergeIn=function(t){var e=sr.call(arguments,1);return this.updateIn(t,zt(),function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]})},ct.prototype.mergeDeep=function(){return xt(this,kt,arguments)},ct.prototype.mergeDeepWith=function(t){var e=sr.call(arguments,1);return xt(this,At(t),e)},ct.prototype.mergeDeepIn=function(t){var e=sr.call(arguments,1);return this.updateIn(t,zt(),function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]})},ct.prototype.sort=function(t){return Zt(we(this,t))},ct.prototype.sortBy=function(t,e){return Zt(we(this,e,t))},ct.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},ct.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new _)},ct.prototype.asImmutable=function(){return this.__ensureOwner()},ct.prototype.wasAltered=function(){return this.__altered},ct.prototype.__iterator=function(t,e){return new mt(this,t,e)},ct.prototype.__iterate=function(t,e){var r=this,n=0;return this._root&&this._root.iterate(function(e){return n++,t(e[1],e[0],r)},e),n},ct.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?St(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},ct.isMap=_t;var Wr="@@__IMMUTABLE_MAP__@@",Cr=ct.prototype;Cr[Wr]=!0,Cr[_r]=Cr.remove,Cr.removeIn=Cr.deleteIn,pt.prototype.get=function(t,e,r,n){for(var i=this.entries,o=0,u=i.length;u>o;o++)if(X(r,i[o][0]))return i[o][1];return n},pt.prototype.update=function(t,e,r,n,i,o,u){for(var s=i===yr,a=this.entries,h=0,f=a.length;f>h&&!X(n,a[h][0]);h++); +var _=f>h;if(_?a[h][1]===i:s)return this;if(c(u),(s||!_)&&c(o),!s||1!==a.length){if(!_&&!s&&a.length>=Nr)return Mt(t,a,n,i);var v=t&&t===this.ownerID,l=v?a:p(a);return _?s?h===f-1?l.pop():l[h]=l.pop():l[h]=[n,i]:l.push([n,i]),v?(this.entries=l,this):new pt(t,l)}},vt.prototype.get=function(t,e,r,n){void 0===e&&(e=ot(r));var i=1<<((0===t?e:e>>>t)&lr),o=this.bitmap;return 0===(o&i)?n:this.nodes[Ut(o&i-1)].get(t+pr,e,r,n)},vt.prototype.update=function(t,e,r,n,i,o,u){void 0===r&&(r=ot(n));var s=(0===e?r:r>>>e)&lr,a=1<=Pr)return Ot(t,_,h,s,v);if(f&&!v&&2===_.length&&qt(_[1^c]))return _[1^c];if(f&&v&&1===_.length&&qt(v))return v;var l=t&&t===this.ownerID,y=f?v?h:h^a:h|a,d=f?v?Kt(_,c,v,l):Tt(_,c,l):Lt(_,c,v,l);return l?(this.bitmap=y,this.nodes=d,this):new vt(t,y,d)},lt.prototype.get=function(t,e,r,n){void 0===e&&(e=ot(r));var i=(0===t?e:e>>>t)&lr,o=this.nodes[i];return o?o.get(t+pr,e,r,n):n},lt.prototype.update=function(t,e,r,n,i,o,u){void 0===r&&(r=ot(n));var s=(0===e?r:r>>>e)&lr,a=i===yr,h=this.nodes,f=h[s];if(a&&!f)return this;var c=bt(f,t,e+pr,r,n,i,o,u);if(c===f)return this;var _=this.count;if(f){if(!c&&(_--,Hr>_))return Et(t,h,_,s)}else _++;var p=t&&t===this.ownerID,v=Kt(h,s,c,p);return p?(this.count=_,this.nodes=v,this):new lt(t,_,v)},yt.prototype.get=function(t,e,r,n){for(var i=this.entries,o=0,u=i.length;u>o;o++)if(X(r,i[o][0]))return i[o][1];return n},yt.prototype.update=function(t,e,r,n,i,o,u){void 0===r&&(r=ot(n));var s=i===yr;if(r!==this.keyHash)return s?this:(c(u),c(o),Dt(this,t,e,r,[n,i]));for(var a=this.entries,h=0,f=a.length;f>h&&!X(n,a[h][0]);h++);var _=f>h;if(_?a[h][1]===i:s)return this;if(c(u),(s||!_)&&c(o),s&&2===f)return new dt(t,this.keyHash,a[1^h]);var v=t&&t===this.ownerID,l=v?a:p(a);return _?s?h===f-1?l.pop():l[h]=l.pop():l[h]=[n,i]:l.push([n,i]),v?(this.entries=l,this):new yt(t,this.keyHash,l)},dt.prototype.get=function(t,e,r,n){return X(r,this.entry[0])?this.entry[1]:n; +},dt.prototype.update=function(t,e,r,n,i,o,u){var s=i===yr,a=X(n,this.entry[0]);return(a?i===this.entry[1]:s)?this:(c(u),s?void c(o):a?t&&t===this.ownerID?(this.entry[1]=i,this):new dt(t,this.keyHash,[n,i]):(c(o),Dt(this,t,e,ot(n),[n,i])))},pt.prototype.iterate=yt.prototype.iterate=function(t,e){for(var r=this.entries,n=0,i=r.length-1;i>=n;n++)if(t(r[e?i-n:n])===!1)return!1},vt.prototype.iterate=lt.prototype.iterate=function(t,e){for(var r=this.nodes,n=0,i=r.length-1;i>=n;n++){var o=r[e?i-n:n];if(o&&o.iterate(t,e)===!1)return!1}},dt.prototype.iterate=function(t,e){return t(this.entry)},t(mt,S),mt.prototype.next=function(){for(var t=this._type,e=this._stack;e;){var r,n=e.node,i=e.index++;if(n.entry){if(0===i)return gt(t,n.entry)}else if(n.entries){if(r=n.entries.length-1,r>=i)return gt(t,n.entries[this._reverse?r-i:i])}else if(r=n.nodes.length-1,r>=i){var o=n.nodes[this._reverse?r-i:i];if(o){if(o.entry)return gt(t,o.entry);e=this._stack=wt(o,e)}continue}e=this._stack=this._stack.__prev}return I()};var Jr,Nr=vr/4,Pr=vr/2,Hr=vr/4;t(Bt,rt),Bt.of=function(){return this(arguments)},Bt.prototype.toString=function(){return this.__toString("List [","]")},Bt.prototype.get=function(t,e){if(t=l(this,t),t>=0&&this.size>t){t+=this._origin;var r=Qt(this,t);return r&&r.array[t&lr]}return e},Bt.prototype.set=function(t,e){return Ht(this,t,e)},Bt.prototype.remove=function(t){return this.has(t)?0===t?this.shift():t===this.size-1?this.pop():this.splice(t,1):this},Bt.prototype.insert=function(t,e){return this.splice(t,0,e)},Bt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=this._origin=this._capacity=0,this._level=pr,this._root=this._tail=null,this.__hash=void 0,this.__altered=!0,this):Pt()},Bt.prototype.push=function(){var t=arguments,e=this.size;return this.withMutations(function(r){Xt(r,0,e+t.length);for(var n=0;t.length>n;n++)r.set(e+n,t[n])})},Bt.prototype.pop=function(){return Xt(this,0,-1)},Bt.prototype.unshift=function(){var t=arguments;return this.withMutations(function(e){Xt(e,-t.length);for(var r=0;t.length>r;r++)e.set(r,t[r]); +})},Bt.prototype.shift=function(){return Xt(this,1)},Bt.prototype.merge=function(){return Ft(this,void 0,arguments)},Bt.prototype.mergeWith=function(t){var e=sr.call(arguments,1);return Ft(this,t,e)},Bt.prototype.mergeDeep=function(){return Ft(this,kt,arguments)},Bt.prototype.mergeDeepWith=function(t){var e=sr.call(arguments,1);return Ft(this,At(t),e)},Bt.prototype.setSize=function(t){return Xt(this,0,t)},Bt.prototype.slice=function(t,e){var r=this.size;return d(t,e,r)?this:Xt(this,m(t,r),g(e,r))},Bt.prototype.__iterator=function(t,e){var r=0,n=Jt(this,e);return new S(function(){var e=n();return e===Xr?I():z(t,r++,e)})},Bt.prototype.__iterate=function(t,e){for(var r,n=0,i=Jt(this,e);(r=i())!==Xr&&t(r,n++,this)!==!1;);return n},Bt.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Nt(this._origin,this._capacity,this._level,this._root,this._tail,t,this.__hash):(this.__ownerID=t,this)},Bt.isList=Wt;var Vr="@@__IMMUTABLE_LIST__@@",Yr=Bt.prototype;Yr[Vr]=!0,Yr[_r]=Yr.remove,Yr.setIn=Cr.setIn,Yr.deleteIn=Yr.removeIn=Cr.removeIn,Yr.update=Cr.update,Yr.updateIn=Cr.updateIn,Yr.mergeIn=Cr.mergeIn,Yr.mergeDeepIn=Cr.mergeDeepIn,Yr.withMutations=Cr.withMutations,Yr.asMutable=Cr.asMutable,Yr.asImmutable=Cr.asImmutable,Yr.wasAltered=Cr.wasAltered,Ct.prototype.removeBefore=function(t,e,r){if(r===e?1<>>e&lr;if(n>=this.array.length)return new Ct([],t);var i,o=0===n;if(e>0){var u=this.array[n];if(i=u&&u.removeBefore(t,e-pr,r),i===u&&o)return this}if(o&&!i)return this;var s=Yt(this,t);if(!o)for(var a=0;n>a;a++)s.array[a]=void 0;return i&&(s.array[n]=i),s},Ct.prototype.removeAfter=function(t,e,r){if(r===(e?1<>>e&lr;if(n>=this.array.length)return this;var i;if(e>0){var o=this.array[n];if(i=o&&o.removeAfter(t,e-pr,r),i===o&&n===this.array.length-1)return this}var u=Yt(this,t);return u.array.splice(n+1),i&&(u.array[n]=i),u};var Qr,Xr={};t(Zt,ct),Zt.of=function(){return this(arguments)},Zt.prototype.toString=function(){return this.__toString("OrderedMap {","}"); +},Zt.prototype.get=function(t,e){var r=this._map.get(t);return void 0!==r?this._list.get(r)[1]:e},Zt.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):ee()},Zt.prototype.set=function(t,e){return re(this,t,e)},Zt.prototype.remove=function(t){return re(this,t,yr)},Zt.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Zt.prototype.__iterate=function(t,e){var r=this;return this._list.__iterate(function(e){return e&&t(e[1],e[0],r)},e)},Zt.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},Zt.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),r=this._list.__ensureOwner(t);return t?te(e,r,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=r,this)},Zt.isOrderedMap=$t,Zt.prototype[cr]=!0,Zt.prototype[_r]=Zt.prototype.remove;var Fr;t(ne,x),ne.prototype.get=function(t,e){return this._iter.get(t,e)},ne.prototype.has=function(t){return this._iter.has(t)},ne.prototype.valueSeq=function(){return this._iter.valueSeq()},ne.prototype.reverse=function(){var t=this,e=he(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},ne.prototype.map=function(t,e){var r=this,n=ae(this,t,e);return this._useKeys||(n.valueSeq=function(){return r._iter.toSeq().map(t,e)}),n},ne.prototype.__iterate=function(t,e){var r,n=this;return this._iter.__iterate(this._useKeys?function(e,r){return t(e,r,n)}:(r=e?De(this):0,function(i){return t(i,e?--r:r++,n)}),e)},ne.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var r=this._iter.__iterator(wr,e),n=e?De(this):0;return new S(function(){var i=r.next();return i.done?i:z(t,e?--n:n++,i.value,i)})},ne.prototype[cr]=!0,t(ie,k),ie.prototype.includes=function(t){return this._iter.includes(t)},ie.prototype.__iterate=function(t,e){var r=this,n=0;return this._iter.__iterate(function(e){return t(e,n++,r)},e)},ie.prototype.__iterator=function(t,e){var r=this._iter.__iterator(wr,e),n=0; +return new S(function(){var e=r.next();return e.done?e:z(t,n++,e.value,e)})},t(oe,A),oe.prototype.has=function(t){return this._iter.includes(t)},oe.prototype.__iterate=function(t,e){var r=this;return this._iter.__iterate(function(e){return t(e,e,r)},e)},oe.prototype.__iterator=function(t,e){var r=this._iter.__iterator(wr,e);return new S(function(){var e=r.next();return e.done?e:z(t,e.value,e.value,e)})},t(ue,x),ue.prototype.entrySeq=function(){return this._iter.toSeq()},ue.prototype.__iterate=function(t,e){var r=this;return this._iter.__iterate(function(e){if(e){qe(e);var n=o(e);return t(n?e.get(1):e[1],n?e.get(0):e[0],r)}},e)},ue.prototype.__iterator=function(t,e){var r=this._iter.__iterator(wr,e);return new S(function(){for(;;){var e=r.next();if(e.done)return e;var n=e.value;if(n){qe(n);var i=o(n);return z(t,i?n.get(0):n[0],i?n.get(1):n[1],e)}}})},ie.prototype.cacheResult=ne.prototype.cacheResult=oe.prototype.cacheResult=ue.prototype.cacheResult=Oe,t(Ae,et),Ae.prototype.toString=function(){return this.__toString(Re(this)+" {","}")},Ae.prototype.has=function(t){return this._defaultValues.hasOwnProperty(t)},Ae.prototype.get=function(t,e){if(!this.has(t))return e;var r=this._defaultValues[t];return this._map?this._map.get(t,r):r},Ae.prototype.clear=function(){if(this.__ownerID)return this._map&&this._map.clear(),this;var t=this.constructor;return t._empty||(t._empty=je(this,zt()))},Ae.prototype.set=function(t,e){if(!this.has(t))throw Error('Cannot set unknown key "'+t+'" on '+Re(this));if(this._map&&!this._map.has(t)){var r=this._defaultValues[t];if(e===r)return this}var n=this._map&&this._map.set(t,e);return this.__ownerID||n===this._map?this:je(this,n)},Ae.prototype.remove=function(t){if(!this.has(t))return this;var e=this._map&&this._map.remove(t);return this.__ownerID||e===this._map?this:je(this,e)},Ae.prototype.wasAltered=function(){return this._map.wasAltered()},Ae.prototype.__iterator=function(t,e){var n=this;return r(this._defaultValues).map(function(t,e){return n.get(e)}).__iterator(t,e)},Ae.prototype.__iterate=function(t,e){ +var n=this;return r(this._defaultValues).map(function(t,e){return n.get(e)}).__iterate(t,e)},Ae.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map&&this._map.__ensureOwner(t);return t?je(this,e,t):(this.__ownerID=t,this._map=e,this)};var Gr=Ae.prototype;Gr[_r]=Gr.remove,Gr.deleteIn=Gr.removeIn=Cr.removeIn,Gr.merge=Cr.merge,Gr.mergeWith=Cr.mergeWith,Gr.mergeIn=Cr.mergeIn,Gr.mergeDeep=Cr.mergeDeep,Gr.mergeDeepWith=Cr.mergeDeepWith,Gr.mergeDeepIn=Cr.mergeDeepIn,Gr.setIn=Cr.setIn,Gr.update=Cr.update,Gr.updateIn=Cr.updateIn,Gr.withMutations=Cr.withMutations,Gr.asMutable=Cr.asMutable,Gr.asImmutable=Cr.asImmutable,t(Le,nt),Le.of=function(){return this(arguments)},Le.fromKeys=function(t){return this(r(t).keySeq())},Le.prototype.toString=function(){return this.__toString("Set {","}")},Le.prototype.has=function(t){return this._map.has(t)},Le.prototype.add=function(t){return Be(this,this._map.set(t,!0))},Le.prototype.remove=function(t){return Be(this,this._map.remove(t))},Le.prototype.clear=function(){return Be(this,this._map.clear())},Le.prototype.union=function(){var t=sr.call(arguments,0);return t=t.filter(function(t){return 0!==t.size}),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations(function(e){for(var r=0;t.length>r;r++)i(t[r]).forEach(function(t){return e.add(t)})}):this.constructor(t[0])},Le.prototype.intersect=function(){var t=sr.call(arguments,0);if(0===t.length)return this;t=t.map(function(t){return i(t)});var e=this;return this.withMutations(function(r){e.forEach(function(e){t.every(function(t){return t.includes(e)})||r.remove(e)})})},Le.prototype.subtract=function(){var t=sr.call(arguments,0);if(0===t.length)return this;t=t.map(function(t){return i(t)});var e=this;return this.withMutations(function(r){e.forEach(function(e){t.some(function(t){return t.includes(e)})&&r.remove(e)})})},Le.prototype.merge=function(){return this.union.apply(this,arguments)},Le.prototype.mergeWith=function(t){var e=sr.call(arguments,1);return this.union.apply(this,e)}, +Le.prototype.sort=function(t){return Je(we(this,t))},Le.prototype.sortBy=function(t,e){return Je(we(this,e,t))},Le.prototype.wasAltered=function(){return this._map.wasAltered()},Le.prototype.__iterate=function(t,e){var r=this;return this._map.__iterate(function(e,n){return t(n,n,r)},e)},Le.prototype.__iterator=function(t,e){return this._map.map(function(t,e){return e}).__iterator(t,e)},Le.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t);return t?this.__make(e,t):(this.__ownerID=t,this._map=e,this)},Le.isSet=Te;var Zr="@@__IMMUTABLE_SET__@@",$r=Le.prototype;$r[Zr]=!0,$r[_r]=$r.remove,$r.mergeDeep=$r.merge,$r.mergeDeepWith=$r.mergeWith,$r.withMutations=Cr.withMutations,$r.asMutable=Cr.asMutable,$r.asImmutable=Cr.asImmutable,$r.__empty=Ce,$r.__make=We;var tn;t(Je,Le),Je.of=function(){return this(arguments)},Je.fromKeys=function(t){return this(r(t).keySeq())},Je.prototype.toString=function(){return this.__toString("OrderedSet {","}")},Je.isOrderedSet=Ne;var en=Je.prototype;en[cr]=!0,en.__empty=He,en.__make=Pe;var rn;t(Ve,rt),Ve.of=function(){return this(arguments)},Ve.prototype.toString=function(){return this.__toString("Stack [","]")},Ve.prototype.get=function(t,e){var r=this._head;for(t=l(this,t);r&&t--;)r=r.next;return r?r.value:e},Ve.prototype.peek=function(){return this._head&&this._head.value},Ve.prototype.push=function(){if(0===arguments.length)return this;for(var t=this.size+arguments.length,e=this._head,r=arguments.length-1;r>=0;r--)e={value:arguments[r],next:e};return this.__ownerID?(this.size=t,this._head=e,this.__hash=void 0,this.__altered=!0,this):Qe(t,e)},Ve.prototype.pushAll=function(t){if(t=n(t),0===t.size)return this;ft(t.size);var e=this.size,r=this._head;return t.reverse().forEach(function(t){e++,r={value:t,next:r}}),this.__ownerID?(this.size=e,this._head=r,this.__hash=void 0,this.__altered=!0,this):Qe(e,r)},Ve.prototype.pop=function(){return this.slice(1)},Ve.prototype.unshift=function(){return this.push.apply(this,arguments)},Ve.prototype.unshiftAll=function(t){ +return this.pushAll(t)},Ve.prototype.shift=function(){return this.pop.apply(this,arguments)},Ve.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Xe()},Ve.prototype.slice=function(t,e){if(d(t,e,this.size))return this;var r=m(t,this.size),n=g(e,this.size);if(n!==this.size)return rt.prototype.slice.call(this,t,e);for(var i=this.size-r,o=this._head;r--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):Qe(i,o)},Ve.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Qe(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Ve.prototype.__iterate=function(t,e){if(e)return this.reverse().__iterate(t);for(var r=0,n=this._head;n&&t(n.value,r++,this)!==!1;)n=n.next;return r},Ve.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var r=0,n=this._head;return new S(function(){if(n){var e=n.value;return n=n.next,z(t,r++,e)}return I()})},Ve.isStack=Ye;var nn="@@__IMMUTABLE_STACK__@@",on=Ve.prototype;on[nn]=!0,on.withMutations=Cr.withMutations,on.asMutable=Cr.asMutable,on.asImmutable=Cr.asImmutable,on.wasAltered=Cr.wasAltered;var un;e.Iterator=S,Fe(e,{toArray:function(){ft(this.size);var t=Array(this.size||0);return this.valueSeq().__iterate(function(e,r){t[r]=e}),t},toIndexedSeq:function(){return new ie(this)},toJS:function(){return this.toSeq().map(function(t){return t&&"function"==typeof t.toJS?t.toJS():t}).__toJS()},toJSON:function(){return this.toSeq().map(function(t){return t&&"function"==typeof t.toJSON?t.toJSON():t}).__toJS()},toKeyedSeq:function(){return new ne(this,!0)},toMap:function(){return ct(this.toKeyedSeq())},toObject:function(){ft(this.size);var t={};return this.__iterate(function(e,r){t[r]=e}),t},toOrderedMap:function(){return Zt(this.toKeyedSeq())},toOrderedSet:function(){return Je(u(this)?this.valueSeq():this)},toSet:function(){return Le(u(this)?this.valueSeq():this)},toSetSeq:function(){return new oe(this); +},toSeq:function(){return s(this)?this.toIndexedSeq():u(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Ve(u(this)?this.valueSeq():this)},toList:function(){return Bt(u(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(t,e){return 0===this.size?t+e:t+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+e},concat:function(){var t=sr.call(arguments,0);return be(this,ye(this,t))},includes:function(t){return this.some(function(e){return X(e,t)})},entries:function(){return this.__iterator(Sr)},every:function(t,e){ft(this.size);var r=!0;return this.__iterate(function(n,i,o){return t.call(e,n,i,o)?void 0:(r=!1,!1)}),r},filter:function(t,e){return be(this,fe(this,t,e,!0))},find:function(t,e,r){var n=this.findEntry(t,e);return n?n[1]:r},forEach:function(t,e){return ft(this.size),this.__iterate(e?t.bind(e):t)},join:function(t){ft(this.size),t=void 0!==t?""+t:",";var e="",r=!0;return this.__iterate(function(n){r?r=!1:e+=t,e+=null!==n&&void 0!==n?""+n:""}),e},keys:function(){return this.__iterator(gr)},map:function(t,e){return be(this,ae(this,t,e))},reduce:function(t,e,r){ft(this.size);var n,i;return arguments.length<2?i=!0:n=e,this.__iterate(function(e,o,u){i?(i=!1,n=e):n=t.call(r,n,e,o,u)}),n},reduceRight:function(t,e,r){var n=this.toKeyedSeq().reverse();return n.reduce.apply(n,arguments)},reverse:function(){return be(this,he(this,!0))},slice:function(t,e){return be(this,pe(this,t,e,!0))},some:function(t,e){return!this.every($e(t),e)},sort:function(t){return be(this,we(this,t))},values:function(){return this.__iterator(wr)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some(function(){return!0})},count:function(t,e){return v(t?this.toSeq().filter(t,e):this)},countBy:function(t,e){return ce(this,t,e)},equals:function(t){return F(this,t)},entrySeq:function(){var t=this;if(t._cache)return new j(t._cache);var e=t.toSeq().map(Ze).toIndexedSeq();return e.fromEntrySeq=function(){return t.toSeq()},e},filterNot:function(t,e){ +return this.filter($e(t),e)},findEntry:function(t,e,r){var n=r;return this.__iterate(function(r,i,o){return t.call(e,r,i,o)?(n=[i,r],!1):void 0}),n},findKey:function(t,e){var r=this.findEntry(t,e);return r&&r[0]},findLast:function(t,e,r){return this.toKeyedSeq().reverse().find(t,e,r)},findLastEntry:function(t,e,r){return this.toKeyedSeq().reverse().findEntry(t,e,r)},findLastKey:function(t,e){return this.toKeyedSeq().reverse().findKey(t,e)},first:function(){return this.find(y)},flatMap:function(t,e){return be(this,me(this,t,e))},flatten:function(t){return be(this,de(this,t,!0))},fromEntrySeq:function(){return new ue(this)},get:function(t,e){return this.find(function(e,r){return X(r,t)},void 0,e)},getIn:function(t,e){for(var r,n=this,i=ke(t);!(r=i.next()).done;){var o=r.value;if(n=n&&n.get?n.get(o,yr):yr,n===yr)return e}return n},groupBy:function(t,e){return _e(this,t,e)},has:function(t){return this.get(t,yr)!==yr},hasIn:function(t){return this.getIn(t,yr)!==yr},isSubset:function(t){return t="function"==typeof t.includes?t:e(t),this.every(function(e){return t.includes(e)})},isSuperset:function(t){return t="function"==typeof t.isSubset?t:e(t),t.isSubset(this)},keyOf:function(t){return this.findKey(function(e){return X(e,t)})},keySeq:function(){return this.toSeq().map(Ge).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(t){return this.toKeyedSeq().reverse().keyOf(t)},max:function(t){return Se(this,t)},maxBy:function(t,e){return Se(this,e,t)},min:function(t){return Se(this,t?tr(t):nr)},minBy:function(t,e){return Se(this,e?tr(e):nr,t)},rest:function(){return this.slice(1)},skip:function(t){return this.slice(Math.max(0,t))},skipLast:function(t){return be(this,this.toSeq().reverse().skip(t).reverse())},skipWhile:function(t,e){return be(this,le(this,t,e,!0))},skipUntil:function(t,e){return this.skipWhile($e(t),e)},sortBy:function(t,e){return be(this,we(this,e,t))},take:function(t){return this.slice(0,Math.max(0,t))},takeLast:function(t){return be(this,this.toSeq().reverse().take(t).reverse()); +},takeWhile:function(t,e){return be(this,ve(this,t,e))},takeUntil:function(t,e){return this.takeWhile($e(t),e)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var sn=e.prototype;sn[ar]=!0,sn[br]=sn.values,sn.__toJS=sn.toArray,sn.__toStringMapper=er,sn.inspect=sn.toSource=function(){return""+this},sn.chain=sn.flatMap,sn.contains=sn.includes,Fe(r,{flip:function(){return be(this,se(this))},mapEntries:function(t,e){var r=this,n=0;return be(this,this.toSeq().map(function(i,o){return t.call(e,[o,i],n++,r)}).fromEntrySeq())},mapKeys:function(t,e){var r=this;return be(this,this.toSeq().flip().map(function(n,i){return t.call(e,n,i,r)}).flip())}});var an=r.prototype;an[hr]=!0,an[br]=sn.entries,an.__toJS=sn.toObject,an.__toStringMapper=function(t,e){return JSON.stringify(e)+": "+er(t)},Fe(n,{toKeyedSeq:function(){return new ne(this,!1)},filter:function(t,e){return be(this,fe(this,t,e,!1))},findIndex:function(t,e){var r=this.findEntry(t,e);return r?r[0]:-1},indexOf:function(t){var e=this.keyOf(t);return void 0===e?-1:e},lastIndexOf:function(t){var e=this.lastKeyOf(t);return void 0===e?-1:e},reverse:function(){return be(this,he(this,!1))},slice:function(t,e){return be(this,pe(this,t,e,!1))},splice:function(t,e){var r=arguments.length;if(e=Math.max(0|e,0),0===r||2===r&&!e)return this;t=m(t,0>t?this.count():this.size);var n=this.slice(0,t);return be(this,1===r?n:n.concat(p(arguments,2),this.slice(t+e)))},findLastIndex:function(t,e){var r=this.findLastEntry(t,e);return r?r[0]:-1},first:function(){return this.get(0)},flatten:function(t){return be(this,de(this,t,!1))},get:function(t,e){return t=l(this,t),0>t||this.size===1/0||void 0!==this.size&&t>this.size?e:this.find(function(e,r){return r===t},void 0,e)},has:function(t){return t=l(this,t),t>=0&&(void 0!==this.size?this.size===1/0||this.size>t:-1!==this.indexOf(t))},interpose:function(t){return be(this,ge(this,t))},interleave:function(){var t=[this].concat(p(arguments)),e=Ie(this.toSeq(),k.of,t),r=e.flatten(!0);return e.size&&(r.size=e.size*t.length), +be(this,r)},keySeq:function(){return $(0,this.size)},last:function(){return this.get(-1)},skipWhile:function(t,e){return be(this,le(this,t,e,!1))},zip:function(){var t=[this].concat(p(arguments));return be(this,Ie(this,rr,t))},zipWith:function(t){var e=p(arguments);return e[0]=this,be(this,Ie(this,t,e))}}),n.prototype[fr]=!0,n.prototype[cr]=!0,Fe(i,{get:function(t,e){return this.has(t)?t:e},includes:function(t){return this.has(t)},keySeq:function(){return this.valueSeq()}}),i.prototype.has=sn.includes,i.prototype.contains=i.prototype.includes,Fe(x,r.prototype),Fe(k,n.prototype),Fe(A,i.prototype),Fe(et,r.prototype),Fe(rt,n.prototype),Fe(nt,i.prototype);var hn={Iterable:e,Seq:O,Collection:tt,Map:ct,OrderedMap:Zt,List:Bt,Stack:Ve,Set:Le,OrderedSet:Je,Record:Ae,Range:$,Repeat:G,is:X,fromJS:H};return hn}); \ No newline at end of file diff --git a/backend/static/drf-yasg/immutable.min.js.gz b/backend/static/drf-yasg/immutable.min.js.gz new file mode 100644 index 0000000..17d0042 Binary files /dev/null and b/backend/static/drf-yasg/immutable.min.js.gz differ diff --git a/backend/static/drf-yasg/insQ.js b/backend/static/drf-yasg/insQ.js new file mode 100644 index 0000000..92b57dc --- /dev/null +++ b/backend/static/drf-yasg/insQ.js @@ -0,0 +1,163 @@ +var insertionQ = (function () { + "use strict"; + + var sequence = 100, + isAnimationSupported = false, + animation_string = 'animationName', + keyframeprefix = '', + domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), + pfx = '', + elm = document.createElement('div'), + options = { + strictlyNew: true, + timeout: 20 + }; + + if (elm.style.animationName) { + isAnimationSupported = true; + } + + if (isAnimationSupported === false) { + for (var i = 0; i < domPrefixes.length; i++) { + if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { + pfx = domPrefixes[i]; + animation_string = pfx + 'AnimationName'; + keyframeprefix = '-' + pfx.toLowerCase() + '-'; + isAnimationSupported = true; + break; + } + } + } + + + function listen(selector, callback) { + var styleAnimation, animationName = 'insQ_' + (sequence++); + + var eventHandler = function (event) { + if (event.animationName === animationName || event[animation_string] === animationName) { + if (!isTagged(event.target)) { + callback(event.target); + } + } + }; + + styleAnimation = document.createElement('style'); + styleAnimation.innerHTML = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }' + + "\n" + selector + ' { animation-duration: 0.001s; animation-name: ' + animationName + '; ' + + keyframeprefix + 'animation-duration: 0.001s; ' + keyframeprefix + 'animation-name: ' + animationName + '; ' + + ' } '; + + document.head.appendChild(styleAnimation); + + var bindAnimationLater = setTimeout(function () { + document.addEventListener('animationstart', eventHandler, false); + document.addEventListener('MSAnimationStart', eventHandler, false); + document.addEventListener('webkitAnimationStart', eventHandler, false); + //event support is not consistent with DOM prefixes + }, options.timeout); //starts listening later to skip elements found on startup. this might need tweaking + + return { + destroy: function () { + clearTimeout(bindAnimationLater); + if (styleAnimation) { + document.head.removeChild(styleAnimation); + styleAnimation = null; + } + document.removeEventListener('animationstart', eventHandler); + document.removeEventListener('MSAnimationStart', eventHandler); + document.removeEventListener('webkitAnimationStart', eventHandler); + } + }; + } + + + function tag(el) { + el.QinsQ = true; //bug in V8 causes memory leaks when weird characters are used as field names. I don't want to risk leaking DOM trees so the key is not '-+-' anymore + } + + function isTagged(el) { + return (options.strictlyNew && (el.QinsQ === true)); + } + + function topmostUntaggedParent(el) { + if (isTagged(el.parentNode)) { + return el; + } else { + return topmostUntaggedParent(el.parentNode); + } + } + + function tagAll(e) { + tag(e); + e = e.firstChild; + for (; e; e = e.nextSibling) { + if (e !== undefined && e.nodeType === 1) { + tagAll(e); + } + } + } + + //aggregates multiple insertion events into a common parent + function catchInsertions(selector, callback) { + var insertions = []; + //throttle summary + var sumUp = (function () { + var to; + return function () { + clearTimeout(to); + to = setTimeout(function () { + insertions.forEach(tagAll); + callback(insertions); + insertions = []; + }, 10); + }; + })(); + + return listen(selector, function (el) { + if (isTagged(el)) { + return; + } + tag(el); + var myparent = topmostUntaggedParent(el); + if (insertions.indexOf(myparent) < 0) { + insertions.push(myparent); + } + sumUp(); + }); + } + + //insQ function + var exports = function (selector) { + if (isAnimationSupported && selector.match(/[^{}]/)) { + + if (options.strictlyNew) { + tagAll(document.body); //prevents from catching things on show + } + return { + every: function (callback) { + return listen(selector, callback); + }, + summary: function (callback) { + return catchInsertions(selector, callback); + } + }; + } else { + return false; + } + }; + + //allows overriding defaults + exports.config = function (opt) { + for (var o in opt) { + if (opt.hasOwnProperty(o)) { + options[o] = opt[o]; + } + } + }; + + return exports; +})(); + +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = insertionQ; +} diff --git a/backend/static/drf-yasg/insQ.js.gz b/backend/static/drf-yasg/insQ.js.gz new file mode 100644 index 0000000..bac0788 Binary files /dev/null and b/backend/static/drf-yasg/insQ.js.gz differ diff --git a/backend/static/drf-yasg/insQ.min.js b/backend/static/drf-yasg/insQ.min.js new file mode 100644 index 0000000..9bef072 --- /dev/null +++ b/backend/static/drf-yasg/insQ.min.js @@ -0,0 +1,4 @@ +// insertion-query v1.0.3 (2016-01-20) +// license:MIT +// Zbyszek Tenerowicz (http://naugtur.pl/) +var insertionQ=function(){"use strict";function a(a,b){var d,e="insQ_"+g++,f=function(a){(a.animationName===e||a[i]===e)&&(c(a.target)||b(a.target))};d=document.createElement("style"),d.innerHTML="@"+j+"keyframes "+e+" { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }\n"+a+" { animation-duration: 0.001s; animation-name: "+e+"; "+j+"animation-duration: 0.001s; "+j+"animation-name: "+e+"; } ",document.head.appendChild(d);var h=setTimeout(function(){document.addEventListener("animationstart",f,!1),document.addEventListener("MSAnimationStart",f,!1),document.addEventListener("webkitAnimationStart",f,!1)},n.timeout);return{destroy:function(){clearTimeout(h),d&&(document.head.removeChild(d),d=null),document.removeEventListener("animationstart",f),document.removeEventListener("MSAnimationStart",f),document.removeEventListener("webkitAnimationStart",f)}}}function b(a){a.QinsQ=!0}function c(a){return n.strictlyNew&&a.QinsQ===!0}function d(a){return c(a.parentNode)?a:d(a.parentNode)}function e(a){for(b(a),a=a.firstChild;a;a=a.nextSibling)void 0!==a&&1===a.nodeType&&e(a)}function f(f,g){var h=[],i=function(){var a;return function(){clearTimeout(a),a=setTimeout(function(){h.forEach(e),g(h),h=[]},10)}}();return a(f,function(a){if(!c(a)){b(a);var e=d(a);h.indexOf(e)<0&&h.push(e),i()}})}var g=100,h=!1,i="animationName",j="",k="Webkit Moz O ms Khtml".split(" "),l="",m=document.createElement("div"),n={strictlyNew:!0,timeout:20};if(m.style.animationName&&(h=!0),h===!1)for(var o=0;o +// This file is licensed under the BSD 3-Clause License. +// License text available at https://opensource.org/licenses/BSD-3-Clause + +"use strict"; + +var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname; +var specURL = currentPath + '?format=openapi'; +var redoc = document.createElement("redoc"); + +var redocSettings = JSON.parse(document.getElementById('redoc-settings').innerHTML); +if (redocSettings.url) { + specURL = redocSettings.url; +} +delete redocSettings.url; +if (redocSettings.fetchSchemaWithQuery) { + var query = new URLSearchParams(window.location.search || '').entries(); + var url = specURL.split('?'); + var usp = new URLSearchParams(url[1] || ''); + for (var it = query.next(); !it.done; it = query.next()) { + usp.set(it.value[0], it.value[1]); + } + url[1] = usp.toString(); + specURL = url[1] ? url.join('?') : url[0]; +} +delete redocSettings.fetchSchemaWithQuery; + +redoc.setAttribute("spec-url", specURL); + +function camelToKebab(str) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} + +for (var p in redocSettings) { + if (redocSettings.hasOwnProperty(p)) { + if (redocSettings[p] !== null && redocSettings[p] !== undefined && redocSettings[p] !== false) { + redoc.setAttribute(camelToKebab(p), redocSettings[p].toString()); + } + } +} + +document.body.replaceChild(redoc, document.getElementById('redoc-placeholder')); + +function hideEmptyVersion() { + // 'span.api-info-version' is for redoc 1.x, 'div.api-info span' is for redoc 2-alpha + var apiVersion = document.querySelector('span.api-info-version') || document.querySelector('div.api-info span'); + if (!apiVersion) { + console.log("WARNING: could not find API versionString element (span.api-info-version)"); + return; + } + + var versionString = apiVersion.innerText; + if (versionString) { + // trim spaces and surrounding () + versionString = versionString.replace(/ /g, ''); + versionString = versionString.replace(/(^\()|(\)$)/g, ''); + } + + if (!versionString) { + // hide version element if empty + apiVersion.classList.add("hidden"); + } +} + +if (document.querySelector('span.api-info-version') || document.querySelector('div.api-info span')) { + hideEmptyVersion(); +} +else { + insertionQ('span.api-info-version').every(hideEmptyVersion); + insertionQ('div.api-info span').every(hideEmptyVersion); +} diff --git a/backend/static/drf-yasg/redoc-init.js.gz b/backend/static/drf-yasg/redoc-init.js.gz new file mode 100644 index 0000000..88bfd6a Binary files /dev/null and b/backend/static/drf-yasg/redoc-init.js.gz differ diff --git a/backend/static/drf-yasg/redoc-old/LICENSE b/backend/static/drf-yasg/redoc-old/LICENSE new file mode 100644 index 0000000..b007060 --- /dev/null +++ b/backend/static/drf-yasg/redoc-old/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015, Rebilly, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/backend/static/drf-yasg/redoc-old/LICENSE.gz b/backend/static/drf-yasg/redoc-old/LICENSE.gz new file mode 100644 index 0000000..5e332a0 Binary files /dev/null and b/backend/static/drf-yasg/redoc-old/LICENSE.gz differ diff --git a/backend/static/drf-yasg/redoc-old/redoc.min.js b/backend/static/drf-yasg/redoc-old/redoc.min.js new file mode 100644 index 0000000..3c8948f --- /dev/null +++ b/backend/static/drf-yasg/redoc-old/redoc.min.js @@ -0,0 +1,8 @@ +/*! + * ReDoc - OpenAPI/Swagger-generated API Reference Documentation + * ------------------------------------------------------------- + * Version: "1.22.3" + * Repo: https://github.com/Rebilly/ReDoc + */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(function(){try{return require("esprima")}catch(e){}}(),function(){try{return require("jquery")}catch(e){}}()):"function"==typeof define&&define.amd?define("Redoc",["esprima","jquery"],t):"object"==typeof exports?exports.Redoc=t(function(){try{return require("esprima")}catch(e){}}(),function(){try{return require("jquery")}catch(e){}}()):e.Redoc=t(e.esprima,e.jquery)}("undefined"!=typeof self?self:this,function(e,t){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=249)}([function(e,t,n){var r=n(5),o=n(8),i=n(25),a=n(20),s=n(58),l=function(e,t,n){var u,c,p,d,f=e&l.F,h=e&l.G,g=e&l.S,m=e&l.P,y=e&l.B,v=h?r:g?r[t]||(r[t]={}):(r[t]||{}).prototype,b=h?o:o[t]||(o[t]={}),_=b.prototype||(b.prototype={});h&&(n=t);for(u in n)c=!f&&v&&void 0!==v[u],p=(c?v:n)[u],d=y&&c?s(p,r):m&&"function"==typeof p?s(Function.call,p):p,v&&a(v,u,p,e&l.U),b[u]!=p&&i(b,u,d),m&&_[u]!=p&&(_[u]=p)};r.core=o,l.F=1,l.G=2,l.S=4,l.P=8,l.B=16,l.W=32,l.U=64,l.R=128,e.exports=l},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){function r(){if(!Do){var e=jo.Symbol;if(e&&e.iterator)Do=e.iterator;else for(var t=Object.getOwnPropertyNames(Map.prototype),n=0;n-1)return t.push(e[n]),t;t.push(e[n])}return t}function k(e){if(e.length>1){return" ("+x(e.slice().reverse()).map(function(e){return a(e.token)}).join(" -> ")+")"}return""}function C(e,t,n,r){var o=[t],i=n(o),a=r?w(i,r):Error(i);return a.addKey=S,a.keys=o,a.injectors=[e],a.constructResolvingMessage=n,a[_i]=r,a}function S(e,t){this.injectors.push(e),this.keys.push(t),this.message=this.constructResolvingMessage(this.keys)}function O(e,t){return C(e,t,function(e){return"No provider for "+a(e[0].token)+"!"+k(e)})}function P(e,t){return C(e,t,function(e){return"Cannot instantiate cyclic dependency!"+k(e)})}function M(e,t,n,r){return C(e,r,function(e){var n=a(e[0].token);return t.message+": Error during instantiation of "+n+"!"+k(e)+"."},t)}function E(e){return Error("Invalid provider - only instances of Provider and Type are allowed, got: "+e)}function T(e,t){for(var n=[],r=0,o=t.length;r-1&&e.splice(n,1)}function ve(e,t){var n=Fa.get(e);if(n)throw new Error("Duplicate module registered for "+e+" - "+n.moduleType.name+" vs "+t.moduleType.name);Fa.set(e,t)}function be(e){var t=Fa.get(e);if(!t)throw new Error("No module with ID "+e+" loaded");return t}function _e(e){return e.reduce(function(e,t){var n=Array.isArray(t)?_e(t):t;return e.concat(n)},[])}function we(e,t,n){if(!e)throw new Error("Cannot find '"+n+"' in '"+t+"'");return e}function xe(e){return e.map(function(e){return e.nativeElement})}function ke(e,t,n){e.childNodes.forEach(function(e){e instanceof Ga&&(t(e)&&n.push(e),ke(e,t,n))})}function Ce(e,t,n){e instanceof Ga&&e.childNodes.forEach(function(e){t(e)&&n.push(e),e instanceof Ga&&Ce(e,t,n)})}function Se(e){return Ja.get(e)||null}function Oe(e){Ja.set(e.nativeNode,e)}function Pe(e){Ja.delete(e.nativeNode)}function Me(e,t){var n=Ee(e),r=Ee(t);if(n&&r)return Te(e,t,Me);var o=e&&("object"==typeof e||"function"==typeof e),a=t&&("object"==typeof t||"function"==typeof t);return!(n||!o||r||!a)||i(e,t)}function Ee(e){return!!Ae(e)&&(Array.isArray(e)||!(e instanceof Map)&&r()in e)}function Te(e,t,n){for(var o=e[r()](),i=t[r()]();;){var a=o.next(),s=i.next();if(a.done&&s.done)return!0;if(a.done||s.done)return!1;if(!n(a.value,s.value))return!1}}function Ie(e,t){if(Array.isArray(e))for(var n=0;n0&&jt(e,t,0,n)&&(f=!0),d>1&&jt(e,t,1,r)&&(f=!0),d>2&&jt(e,t,2,o)&&(f=!0),d>3&&jt(e,t,3,i)&&(f=!0),d>4&&jt(e,t,4,a)&&(f=!0),d>5&&jt(e,t,5,s)&&(f=!0),d>6&&jt(e,t,6,l)&&(f=!0),d>7&&jt(e,t,7,u)&&(f=!0),d>8&&jt(e,t,8,c)&&(f=!0),d>9&&jt(e,t,9,p)&&(f=!0),f}function Nt(e,t,n){for(var r=!1,o=0;o0?o[n-1]:null,r)}function Xt(e,t){var n=at(t);if(n&&n!==e&&!(16&t.state)){t.state|=16;var r=n.template._projectedViews;r||(r=n.template._projectedViews=[]),r.push(t),Gt(t.parent.def,t.parentNodeDef)}}function Gt(e,t){if(!(4&t.flags)){e.nodeFlags|=4,t.flags|=4;for(var n=t.parent;n;)n.childFlags|=4,n=n.parent}}function Jt(e,t){var n=e.viewContainer._embeddedViews;if((null==t||t>=n.length)&&(t=n.length-1),t<0)return null;var r=n[t];return r.viewContainerParent=null,rn(n,t),Os.dirtyParentQueries(r),tn(r),r}function Kt(e){if(16&e.state){var t=at(e);if(t){var n=t.template._projectedViews;n&&(rn(n,n.indexOf(e)),Os.dirtyParentQueries(e))}}}function Qt(e,t,n){var r=e.viewContainer._embeddedViews,o=r[t];return rn(r,t),null==n&&(n=r.length),nn(r,n,o),Os.dirtyParentQueries(o),tn(o),en(e,n>0?r[n-1]:null,o),o}function en(e,t,n){var r=t?lt(t,t.def.lastRenderRootNode):e.renderElement;vt(n,2,n.renderer.parentNode(r),n.renderer.nextSibling(r),void 0)}function tn(e){vt(e,3,null,null,void 0)}function nn(e,t,n){t>=e.length?e.push(n):e.splice(t,0,n)}function rn(e,t){t>=e.length-1?e.pop():e.splice(t,1)}function on(e,t,n,r,o,i){return new Vs(e,t,n,r,o,i)}function an(e){return e.viewDefFactory}function sn(e,t,n){return new Hs(e,t,n)}function ln(e){return new qs(e)}function un(e,t){return new Us(e,t)}function cn(e,t){return new $s(e,t)}function pn(e,t){var n=e.def.nodes[t];if(1&n.flags){var r=Ve(e,n.nodeIndex);return n.element.template?r.template:r.renderElement}if(2&n.flags)return ze(e,n.nodeIndex).renderText;if(20240&n.flags)return Be(e,n.nodeIndex).instance;throw new Error("Illegal state: read nodeValue for node index "+t)}function dn(e){return new Ys(e.renderer)}function fn(e,t,n,r){return new Ws(e,t,n,r)}function hn(e,t,n,r,o,i,a,s){var l=[];if(a)for(var u in a){var c=a[u],p=c[0],d=c[1];l[p]={flags:8,name:u,nonMinifiedName:d,ns:null,securityContext:null,suffix:null}}var f=[];if(s)for(var h in s)f.push({type:1,propName:h,target:null,eventName:s[h]});return t|=16384,yn(e,t,n,r,o,o,i,l,f)}function gn(e,t,n){return e|=16,yn(-1,e,null,0,t,t,n)}function mn(e,t,n,r,o){return yn(-1,e,t,0,n,r,o)}function yn(e,t,n,r,o,i,a,s,l){var u=ft(n),c=u.matchedQueries,p=u.references,d=u.matchedQueryIds;l||(l=[]),s||(s=[]);var f=ht(a);return{nodeIndex:-1,parent:null,renderParent:null,bindingIndex:-1,outputIndex:-1,checkIndex:e,flags:t,childFlags:0,directChildFlags:0,childMatchedQueries:0,matchedQueries:c,matchedQueryIds:d,references:p,ngContentIndex:-1,childCount:r,bindings:s,bindingFlags:Ct(s),outputs:l,element:null,provider:{token:o,value:i,deps:f},text:null,query:null,ngContent:null}}function vn(e,t){return Cn(e,t)}function bn(e,t){for(var n=e;n.parent&&!ct(n);)n=n.parent;return Sn(n.parent,st(n),!0,t.provider.value,t.provider.deps)}function _n(e,t){var n=(32768&t.flags)>0,r=Sn(e,t.parent,n,t.provider.value,t.provider.deps);if(t.outputs.length)for(var o=0;o0&&et(e,t,0,n)&&(h=!0,g=En(e,d,t,0,n,g)),m>1&&et(e,t,1,r)&&(h=!0,g=En(e,d,t,1,r,g)),m>2&&et(e,t,2,o)&&(h=!0,g=En(e,d,t,2,o,g)),m>3&&et(e,t,3,i)&&(h=!0,g=En(e,d,t,3,i,g)),m>4&&et(e,t,4,a)&&(h=!0,g=En(e,d,t,4,a,g)),m>5&&et(e,t,5,s)&&(h=!0,g=En(e,d,t,5,s,g)),m>6&&et(e,t,6,l)&&(h=!0,g=En(e,d,t,6,l,g)),m>7&&et(e,t,7,u)&&(h=!0,g=En(e,d,t,7,u,g)),m>8&&et(e,t,8,c)&&(h=!0,g=En(e,d,t,8,c,g)),m>9&&et(e,t,9,p)&&(h=!0,g=En(e,d,t,9,p,g)),g&&f.ngOnChanges(g),2&e.state&&65536&t.flags&&f.ngOnInit(),262144&t.flags&&f.ngDoCheck(),h}function kn(e,t,n){for(var r=Be(e,t.nodeIndex),o=r.instance,i=!1,a=void 0,s=0;s0,r=t.provider;switch(201347067&t.flags){case 512:return Sn(e,t.parent,n,r.value,r.deps);case 1024:return On(e,t.parent,n,r.value,r.deps);case 2048:return Pn(e,t.parent,n,r.deps[0]);case 256:return r.value}}function Sn(e,t,n,r,o){var i=o.length;switch(i){case 0:return new r;case 1:return new r(Pn(e,t,n,o[0]));case 2:return new r(Pn(e,t,n,o[0]),Pn(e,t,n,o[1]));case 3:return new r(Pn(e,t,n,o[0]),Pn(e,t,n,o[1]),Pn(e,t,n,o[2]));default:for(var a=new Array(i),s=0;s0&&tt(e,t,0,n)&&(f=!0),h>1&&tt(e,t,1,r)&&(f=!0),h>2&&tt(e,t,2,o)&&(f=!0),h>3&&tt(e,t,3,i)&&(f=!0),h>4&&tt(e,t,4,a)&&(f=!0),h>5&&tt(e,t,5,s)&&(f=!0),h>6&&tt(e,t,6,l)&&(f=!0),h>7&&tt(e,t,7,u)&&(f=!0),h>8&&tt(e,t,8,c)&&(f=!0),h>9&&tt(e,t,9,p)&&(f=!0),f){var g=He(e,t.nodeIndex),m=void 0;switch(201347067&t.flags){case 32:m=new Array(d.length),h>0&&(m[0]=n),h>1&&(m[1]=r),h>2&&(m[2]=o),h>3&&(m[3]=i),h>4&&(m[4]=a),h>5&&(m[5]=s),h>6&&(m[6]=l),h>7&&(m[7]=u),h>8&&(m[8]=c),h>9&&(m[9]=p);break;case 64:m={},h>0&&(m[d[0].name]=n),h>1&&(m[d[1].name]=r),h>2&&(m[d[2].name]=o),h>3&&(m[d[3].name]=i),h>4&&(m[d[4].name]=a),h>5&&(m[d[5].name]=s),h>6&&(m[d[6].name]=l),h>7&&(m[d[7].name]=u),h>8&&(m[d[8].name]=c),h>9&&(m[d[9].name]=p);break;case 128:var y=n;switch(h){case 1:m=y.transform(n);break;case 2:m=y.transform(r);break;case 3:m=y.transform(r,o);break;case 4:m=y.transform(r,o,i);break;case 5:m=y.transform(r,o,i,a);break;case 6:m=y.transform(r,o,i,a,s);break;case 7:m=y.transform(r,o,i,a,s,l);break;case 8:m=y.transform(r,o,i,a,s,l,u);break;case 9:m=y.transform(r,o,i,a,s,l,u,c);break;case 10:m=y.transform(r,o,i,a,s,l,u,c,p)}}g.value=m}return f}function Wn(e,t,n){for(var r=t.bindings,o=!1,i=0;i0&&tt(e,t,0,n)&&(d=!0),h>1&&tt(e,t,1,r)&&(d=!0),h>2&&tt(e,t,2,o)&&(d=!0),h>3&&tt(e,t,3,i)&&(d=!0),h>4&&tt(e,t,4,a)&&(d=!0),h>5&&tt(e,t,5,s)&&(d=!0),h>6&&tt(e,t,6,l)&&(d=!0),h>7&&tt(e,t,7,u)&&(d=!0),h>8&&tt(e,t,8,c)&&(d=!0),h>9&&tt(e,t,9,p)&&(d=!0),d){var g=t.text.prefix;h>0&&(g+=Kn(n,f[0])),h>1&&(g+=Kn(r,f[1])),h>2&&(g+=Kn(o,f[2])),h>3&&(g+=Kn(i,f[3])),h>4&&(g+=Kn(a,f[4])),h>5&&(g+=Kn(s,f[5])),h>6&&(g+=Kn(l,f[6])),h>7&&(g+=Kn(u,f[7])),h>8&&(g+=Kn(c,f[8])),h>9&&(g+=Kn(p,f[9]));var m=ze(e,t.nodeIndex).renderText;e.renderer.setValue(m,g)}return d}function Jn(e,t,n){for(var r=t.bindings,o=!1,i=0;i0)u=g,er(g)||(c=g);else for(;u&&h===u.nodeIndex+u.childCount;){var b=u.parent;b&&(b.childFlags|=u.childFlags,b.childMatchedQueries|=u.childMatchedQueries),u=b,c=u&&er(u)?u.renderParent:u}}var _=function(e,n,r,o){return t[n].element.handleEvent(e,r,o)};return{factory:null,nodeFlags:a,rootNodeFlags:s,nodeMatchedQueries:l,flags:e,nodes:t,updateDirectives:n||Ps,updateRenderer:r||Ps,handleEvent:_,bindingCount:o,outputCount:i,lastRenderRootNode:f}}function er(e){return 0!=(1&e.flags)&&null===e.element.name}function tr(e,t,n){var r=t.element&&t.element.template;if(r){if(!r.lastRenderRootNode)throw new Error("Illegal State: Embedded templates without nodes are not allowed!");if(r.lastRenderRootNode&&16777216&r.lastRenderRootNode.flags)throw new Error("Illegal State: Last root node of a template can't have embedded views, at index "+t.nodeIndex+"!")}if(20224&t.flags){if(0==(1&(e?e.flags:0)))throw new Error("Illegal State: StaticProvider/Directive nodes need to be children of elements or anchors, at index "+t.nodeIndex+"!")}if(t.query){if(67108864&t.flags&&(!e||0==(16384&e.flags)))throw new Error("Illegal State: Content Query nodes need to be children of directives, at index "+t.nodeIndex+"!");if(134217728&t.flags&&e)throw new Error("Illegal State: View Query nodes have to be top level nodes, at index "+t.nodeIndex+"!")}if(t.childCount){var o=e?e.nodeIndex+e.childCount:n-1;if(t.nodeIndex<=o&&t.nodeIndex+t.childCount>o)throw new Error("Illegal State: childCount of node leads outside of parent, at index "+t.nodeIndex+"!")}}function nr(e,t,n,r){var o=ir(e.root,e.renderer,e,t,n);return ar(o,e.component,r),sr(o),o}function rr(e,t,n){var r=ir(e,e.renderer,null,null,t);return ar(r,n,n),sr(r),r}function or(e,t,n,r){var o,i=t.element.componentRendererType;return o=i?e.root.rendererFactory.createRenderer(r,i):e.root.renderer,ir(e.root,o,e,t.element.componentProvider,n)}function ir(e,t,n,r,o){var i=new Array(o.nodes.length),a=o.outputCount?new Array(o.outputCount):null;return{def:o,parent:n,viewContainerParent:null,parentNodeDef:r,context:null,component:null,nodes:i,state:13,root:e,renderer:t,oldValues:new Array(o.bindingCount),disposables:a}}function ar(e,t,n){e.component=t,e.context=n}function sr(e){var t;if(ct(e)){var n=e.parentNodeDef;t=Ve(e.parent,n.parent.nodeIndex).renderElement}for(var r=e.def,o=e.nodes,i=0;i0&&nt(e,t,0,n),d>1&&nt(e,t,1,r),d>2&&nt(e,t,2,o),d>3&&nt(e,t,3,i),d>4&&nt(e,t,4,a),d>5&&nt(e,t,5,s),d>6&&nt(e,t,6,l),d>7&&nt(e,t,7,u),d>8&&nt(e,t,8,c),d>9&&nt(e,t,9,p)}function mr(e,t,n){for(var r=0;r=this._providers.length)throw I(e);return this._providers[e]},e.prototype._new=function(e){if(this._constructionCounter++>this._getMaxNumberOfObjects())throw P(this,e.key);return this._instantiateProvider(e)},e.prototype._getMaxNumberOfObjects=function(){return this.objs.length},e.prototype._instantiateProvider=function(e){if(e.multiProvider){for(var t=new Array(e.resolvedFactories.length),n=0;n0)e._bootstrapComponents.forEach(function(e){return t.bootstrap(e)});else{if(!e.instance.ngDoBootstrap)throw new Error("The module "+a(e.instance.constructor)+' was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. Please define one of these.');e.instance.ngDoBootstrap(t)}this._modules.push(e)},t}(Sa);Oa.decorators=[{type:ci}],Oa.ctorParameters=function(){return[{type:yi}]};var Pa=function(){function e(){}return e.prototype.bootstrap=function(e,t){},e.prototype.tick=function(){},e.prototype.componentTypes=function(){},e.prototype.components=function(){},e.prototype.attachView=function(e){},e.prototype.detachView=function(e){},e.prototype.viewCount=function(){},e.prototype.isStable=function(){},e}(),Ma=function(e){function t(t,n,r,i,a,s){var l=e.call(this)||this;l._zone=t,l._console=n,l._injector=r,l._exceptionHandler=i,l._componentFactoryResolver=a,l._initStatus=s,l._bootstrapListeners=[],l._rootComponents=[],l._rootComponentTypes=[],l._views=[],l._runningTick=!1,l._enforceNoNewChanges=!1,l._stable=!0,l._enforceNoNewChanges=ce(),l._zone.onMicrotaskEmpty.subscribe({next:function(){l._zone.run(function(){l.tick()})}});var u=new Oo.Observable(function(e){l._stable=l._zone.isStable&&!l._zone.hasPendingMacrotasks&&!l._zone.hasPendingMicrotasks,l._zone.runOutsideAngular(function(){e.next(l._stable),e.complete()})}),c=new Oo.Observable(function(e){var t;l._zone.runOutsideAngular(function(){t=l._zone.onStable.subscribe(function(){ga.assertNotInAngularZone(),o(function(){l._stable||l._zone.hasPendingMacrotasks||l._zone.hasPendingMicrotasks||(l._stable=!0,e.next(!0))})})});var n=l._zone.onUnstable.subscribe(function(){ga.assertInAngularZone(),l._stable&&(l._stable=!1,l._zone.runOutsideAngular(function(){e.next(!1)}))});return function(){t.unsubscribe(),n.unsubscribe()}});return l._isStable=Object(Po.merge)(u,Mo.share.call(c)),l}return So.a(t,e),t.prototype.attachView=function(e){var t=e;this._views.push(t),t.attachToAppRef(this)},t.prototype.detachView=function(e){var t=e;ye(this._views,t),t.detachFromAppRef()},t.prototype.bootstrap=function(e,t){var n=this;if(!this._initStatus.done)throw new Error("Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.");var r;r=e instanceof Qi?e:this._componentFactoryResolver.resolveComponentFactory(e),this._rootComponentTypes.push(r.componentType);var o=r instanceof aa?null:this._injector.get(sa),i=t||r.selector,a=r.create(yi.NULL,[],i,o);a.onDestroy(function(){n._unloadComponent(a)});var s=a.injector.get(ma,null);return s&&a.injector.get(ya).registerApplication(a.location.nativeElement,s),this._loadComponent(a),ce()&&this._console.log("Angular is running in the development mode. Call enableProdMode() to enable the production mode."),a},t.prototype._loadComponent=function(e){this.attachView(e.hostView),this.tick(),this._rootComponents.push(e),this._injector.get($i,[]).concat(this._bootstrapListeners).forEach(function(t){return t(e)})},t.prototype._unloadComponent=function(e){this.detachView(e.hostView),ye(this._rootComponents,e)},t.prototype.tick=function(){var e=this;if(this._runningTick)throw new Error("ApplicationRef.tick is called recursively");var n=t._tickScope();try{this._runningTick=!0,this._views.forEach(function(e){return e.detectChanges()}),this._enforceNoNewChanges&&this._views.forEach(function(e){return e.checkNoChanges()})}catch(t){this._zone.runOutsideAngular(function(){return e._exceptionHandler.handleError(t)})}finally{this._runningTick=!1,pa(n)}},t.prototype.ngOnDestroy=function(){this._views.slice().forEach(function(e){return e.destroy()})},Object.defineProperty(t.prototype,"viewCount",{get:function(){return this._views.length},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"componentTypes",{get:function(){return this._rootComponentTypes},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"components",{get:function(){return this._rootComponents},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isStable",{get:function(){return this._isStable},enumerable:!0,configurable:!0}),t}(Pa);Ma._tickScope=ca("ApplicationRef#tick()"),Ma.decorators=[{type:ci}],Ma.ctorParameters=function(){return[{type:ga},{type:Wi},{type:yi},{type:xi},{type:na},{type:Vi}]};var Ea=function(){function e(e,t,n,r,o,i){this.id=e,this.templateUrl=t,this.slotCount=n,this.encapsulation=r,this.styles=o,this.animations=i}return e}(),Ta=function(){function e(){}return e.prototype.injector=function(){},e.prototype.component=function(){},e.prototype.providerTokens=function(){},e.prototype.references=function(){},e.prototype.context=function(){},e.prototype.source=function(){},e}(),Ia=function(){function e(){}return e.prototype.selectRootElement=function(e,t){},e.prototype.createElement=function(e,t,n){},e.prototype.createViewRoot=function(e){},e.prototype.createTemplateAnchor=function(e,t){},e.prototype.createText=function(e,t,n){},e.prototype.projectNodes=function(e,t){},e.prototype.attachViewAfter=function(e,t){},e.prototype.detachView=function(e){},e.prototype.destroyView=function(e,t){},e.prototype.listen=function(e,t,n){},e.prototype.listenGlobal=function(e,t,n){},e.prototype.setElementProperty=function(e,t,n){},e.prototype.setElementAttribute=function(e,t,n){},e.prototype.setBindingDebugInfo=function(e,t,n){},e.prototype.setElementClass=function(e,t,n){},e.prototype.setElementStyle=function(e,t,n){},e.prototype.invokeElementMethod=function(e,t,n){},e.prototype.setText=function(e,t){},e.prototype.animate=function(e,t,n,r,o,i,a){},e}(),Aa=(new Io("Renderer2Interceptor"),function(){function e(){}return e.prototype.renderComponent=function(e){},e}()),Ra=function(){function e(){}return e.prototype.createRenderer=function(e,t){},e.prototype.begin=function(){},e.prototype.end=function(){},e.prototype.whenRenderingDone=function(){},e}(),Na={};Na.Important=1,Na.DashCase=2,Na[Na.Important]="Important",Na[Na.DashCase]="DashCase";var ja=function(){function e(){}return e.prototype.data=function(){},e.prototype.destroy=function(){},e.prototype.createElement=function(e,t){},e.prototype.createComment=function(e){},e.prototype.createText=function(e){},e.prototype.appendChild=function(e,t){},e.prototype.insertBefore=function(e,t,n){},e.prototype.removeChild=function(e,t){},e.prototype.selectRootElement=function(e){},e.prototype.parentNode=function(e){},e.prototype.nextSibling=function(e){},e.prototype.setAttribute=function(e,t,n,r){},e.prototype.removeAttribute=function(e,t,n){},e.prototype.addClass=function(e,t){},e.prototype.removeClass=function(e,t){},e.prototype.setStyle=function(e,t,n,r){},e.prototype.removeStyle=function(e,t,n){},e.prototype.setProperty=function(e,t,n){},e.prototype.setValue=function(e,t){},e.prototype.listen=function(e,t,n){},e}(),Da=function(){function e(e){this.nativeElement=e}return e}(),La=function(){function e(){}return e.prototype.load=function(e){},e}(),Fa=new Map,za=function(){function e(){this._dirty=!0,this._results=[],this._emitter=new ha}return Object.defineProperty(e.prototype,"changes",{get:function(){return this._emitter},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"length",{get:function(){return this._results.length},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"first",{get:function(){return this._results[0]},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"last",{get:function(){return this._results[this.length-1]},enumerable:!0,configurable:!0}),e.prototype.map=function(e){return this._results.map(e)},e.prototype.filter=function(e){return this._results.filter(e)},e.prototype.find=function(e){return this._results.find(e)},e.prototype.reduce=function(e,t){return this._results.reduce(e,t)},e.prototype.forEach=function(e){this._results.forEach(e)},e.prototype.some=function(e){return this._results.some(e)},e.prototype.toArray=function(){return this._results.slice()},e.prototype[r()]=function(){return this._results[r()]()},e.prototype.toString=function(){return this._results.toString()},e.prototype.reset=function(e){this._results=_e(e),this._dirty=!1},e.prototype.notifyOnChanges=function(){this._emitter.emit(this)},e.prototype.setDirty=function(){this._dirty=!0},Object.defineProperty(e.prototype,"dirty",{get:function(){return this._dirty},enumerable:!0,configurable:!0}),e.prototype.destroy=function(){this._emitter.complete(),this._emitter.unsubscribe()},e}(),Va=function(){function e(){}return e}(),Ba={factoryPathPrefix:"",factoryPathSuffix:".ngfactory"},Ha=function(){function e(e,t){this._compiler=e,this._config=t||Ba}return e.prototype.load=function(e){return this._compiler instanceof Xi?this.loadFactory(e):this.loadAndCompile(e)},e.prototype.loadAndCompile=function(e){var t=this,r=e.split("#"),o=r[0],i=r[1];return void 0===i&&(i="default"),n(216)(o).then(function(e){return e[i]}).then(function(e){return we(e,o,i)}).then(function(e){return t._compiler.compileModuleAsync(e)})},e.prototype.loadFactory=function(e){var t=e.split("#"),r=t[0],o=t[1],i="NgFactory";return void 0===o&&(o="default",i=""),n(216)(this._config.factoryPathPrefix+r+this._config.factoryPathSuffix).then(function(e){return e[o+i]}).then(function(e){return we(e,r,o)})},e}();Ha.decorators=[{type:ci}],Ha.ctorParameters=function(){return[{type:Xi},{type:Va,decorators:[{type:ui}]}]};var qa=function(){function e(){}return e.prototype.elementRef=function(){},e.prototype.createEmbeddedView=function(e){},e}(),Ua=function(){function e(){}return e.prototype.element=function(){},e.prototype.injector=function(){},e.prototype.parentInjector=function(){},e.prototype.clear=function(){},e.prototype.get=function(e){},e.prototype.length=function(){},e.prototype.createEmbeddedView=function(e,t,n){},e.prototype.createComponent=function(e,t,n,r,o){},e.prototype.insert=function(e,t){},e.prototype.move=function(e,t){},e.prototype.indexOf=function(e){},e.prototype.remove=function(e){},e.prototype.detach=function(e){},e}(),$a=function(){function e(){}return e.prototype.markForCheck=function(){},e.prototype.detach=function(){},e.prototype.detectChanges=function(){},e.prototype.checkNoChanges=function(){},e.prototype.reattach=function(){},e}(),Ya=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return So.a(t,e),t.prototype.destroy=function(){},t.prototype.destroyed=function(){},t.prototype.onDestroy=function(e){},t}($a),Wa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return So.a(t,e),t.prototype.context=function(){},t.prototype.rootNodes=function(){},t}(Ya),Za=function(){function e(e,t){this.name=e,this.callback=t}return e}(),Xa=function(){function e(e,t,n){this._debugContext=n,this.nativeNode=e,t&&t instanceof Ga?t.addChild(this):this.parent=null,this.listeners=[]}return Object.defineProperty(e.prototype,"injector",{get:function(){return this._debugContext.injector},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"componentInstance",{get:function(){return this._debugContext.component},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"context",{get:function(){return this._debugContext.context},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"references",{get:function(){return this._debugContext.references},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"providerTokens",{get:function(){return this._debugContext.providerTokens},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"source",{get:function(){return"Deprecated since v4"},enumerable:!0,configurable:!0}),e}(),Ga=function(e){function t(t,n,r){var o=e.call(this,t,n,r)||this;return o.properties={},o.attributes={},o.classes={},o.styles={},o.childNodes=[],o.nativeElement=t,o}return So.a(t,e),t.prototype.addChild=function(e){e&&(this.childNodes.push(e),e.parent=this)},t.prototype.removeChild=function(e){var t=this.childNodes.indexOf(e);-1!==t&&(e.parent=null,this.childNodes.splice(t,1))},t.prototype.insertChildrenAfter=function(e,t){var n=this,r=this.childNodes.indexOf(e);-1!==r&&((o=this.childNodes).splice.apply(o,[r+1,0].concat(t)),t.forEach(function(e){e.parent&&e.parent.removeChild(e),e.parent=n}));var o},t.prototype.insertBefore=function(e,t){var n=this.childNodes.indexOf(e);-1===n?this.addChild(t):(t.parent&&t.parent.removeChild(t),t.parent=this,this.childNodes.splice(n,0,t))},t.prototype.query=function(e){return this.queryAll(e)[0]||null},t.prototype.queryAll=function(e){var t=[];return ke(this,e,t),t},t.prototype.queryAllNodes=function(e){var t=[];return Ce(this,e,t),t},Object.defineProperty(t.prototype,"children",{get:function(){return this.childNodes.filter(function(e){return e instanceof t})},enumerable:!0,configurable:!0}),t.prototype.triggerEventHandler=function(e,t){this.listeners.forEach(function(n){n.name==e&&n.callback(t)})},t}(Xa),Ja=new Map,Ka=function(){function e(e){this.wrapped=e}return e.wrap=function(t){return new e(t)},e}(),Qa=function(){function e(){this.hasWrappedValue=!1}return e.prototype.unwrap=function(e){return e instanceof Ka?(this.hasWrappedValue=!0,e.wrapped):e},e.prototype.reset=function(){this.hasWrappedValue=!1},e}(),es=function(){function e(e,t,n){this.previousValue=e,this.currentValue=t,this.firstChange=n}return e.prototype.isFirstChange=function(){return this.firstChange},e}(),ts=function(){function e(){}return e.prototype.supports=function(e){return Ee(e)},e.prototype.create=function(e,t){return new rs(t||e)},e}(),ns=function(e,t){return t},rs=function(){function e(e){this._length=0,this._collection=null,this._linkedRecords=null,this._unlinkedRecords=null,this._previousItHead=null,this._itHead=null,this._itTail=null,this._additionsHead=null,this._additionsTail=null,this._movesHead=null,this._movesTail=null,this._removalsHead=null,this._removalsTail=null,this._identityChangesHead=null,this._identityChangesTail=null,this._trackByFn=e||ns}return Object.defineProperty(e.prototype,"collection",{get:function(){return this._collection},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"length",{get:function(){return this._length},enumerable:!0,configurable:!0}),e.prototype.forEachItem=function(e){var t;for(t=this._itHead;null!==t;t=t._next)e(t)},e.prototype.forEachOperation=function(e){for(var t=this._itHead,n=this._removalsHead,r=0,o=null;t||n;){var i=!n||t&&t.currentIndex"+a(this.currentIndex)+"]"},e}(),is=function(){function e(){this._head=null,this._tail=null}return e.prototype.add=function(e){null===this._head?(this._head=this._tail=e,e._nextDup=null,e._prevDup=null):(this._tail._nextDup=e,e._prevDup=this._tail,e._nextDup=null,this._tail=e)},e.prototype.get=function(e,t){var n;for(n=this._head;null!==n;n=n._nextDup)if((null===t||t<=n.currentIndex)&&i(n.trackById,e))return n;return null},e.prototype.remove=function(e){var t=e._prevDup,n=e._nextDup;return null===t?this._head=n:t._nextDup=n,null===n?this._tail=t:n._prevDup=t,null===this._head},e}(),as=function(){function e(){this.map=new Map}return e.prototype.put=function(e){var t=e.trackById,n=this.map.get(t);n||(n=new is,this.map.set(t,n)),n.add(e)},e.prototype.get=function(e,t){var n=e,r=this.map.get(n);return r?r.get(e,t):null},e.prototype.remove=function(e){var t=e.trackById;return this.map.get(t).remove(e)&&this.map.delete(t),e},Object.defineProperty(e.prototype,"isEmpty",{get:function(){return 0===this.map.size},enumerable:!0,configurable:!0}),e.prototype.clear=function(){this.map.clear()},e.prototype.toString=function(){return"_DuplicateMap("+a(this.map)+")"},e}(),ss=function(){function e(){}return e.prototype.supports=function(e){return e instanceof Map||Ae(e)},e.prototype.create=function(e){return new ls},e}(),ls=function(){function e(){this._records=new Map,this._mapHead=null,this._appendAfter=null,this._previousMapHead=null,this._changesHead=null,this._changesTail=null,this._additionsHead=null,this._additionsTail=null,this._removalsHead=null,this._removalsTail=null}return Object.defineProperty(e.prototype,"isDirty",{get:function(){return null!==this._additionsHead||null!==this._changesHead||null!==this._removalsHead},enumerable:!0,configurable:!0}),e.prototype.forEachItem=function(e){var t;for(t=this._mapHead;null!==t;t=t._next)e(t)},e.prototype.forEachPreviousItem=function(e){var t;for(t=this._previousMapHead;null!==t;t=t._nextPrevious)e(t)},e.prototype.forEachChangedItem=function(e){var t;for(t=this._changesHead;null!==t;t=t._nextChanged)e(t)},e.prototype.forEachAddedItem=function(e){var t;for(t=this._additionsHead;null!==t;t=t._nextAdded)e(t)},e.prototype.forEachRemovedItem=function(e){var t;for(t=this._removalsHead;null!==t;t=t._nextRemoved)e(t)},e.prototype.diff=function(e){if(e){if(!(e instanceof Map||Ae(e)))throw new Error("Error trying to diff '"+a(e)+"'. Only maps and objects are allowed")}else e=new Map;return this.check(e)?this:null},e.prototype.onDestroy=function(){},e.prototype.check=function(e){var t=this;this._reset();var n=this._mapHead;if(this._appendAfter=null,this._forEach(e,function(e,r){if(n&&n.key===r)t._maybeAddToChanges(n,e),t._appendAfter=n,n=n._next;else{var o=t._getOrCreateRecordForKey(r,e);n=t._insertBeforeOrAppend(n,o)}}),n){n._prev&&(n._prev._next=null),this._removalsHead=n;for(var r=n;null!==r;r=r._nextRemoved)r===this._mapHead&&(this._mapHead=null),this._records.delete(r.key),r._nextRemoved=r._next,r.previousValue=r.currentValue,r.currentValue=null,r._prev=null,r._next=null}return this._changesTail&&(this._changesTail._nextChanged=null),this._additionsTail&&(this._additionsTail._nextAdded=null),this.isDirty},e.prototype._insertBeforeOrAppend=function(e,t){if(e){var n=e._prev;return t._next=e,t._prev=n,e._prev=t,n&&(n._next=t),e===this._mapHead&&(this._mapHead=t),this._appendAfter=e,e}return this._appendAfter?(this._appendAfter._next=t,t._prev=this._appendAfter):this._mapHead=t,this._appendAfter=t,null},e.prototype._getOrCreateRecordForKey=function(e,t){if(this._records.has(e)){var n=this._records.get(e);this._maybeAddToChanges(n,t);var r=n._prev,o=n._next;return r&&(r._next=o),o&&(o._prev=r),n._next=null,n._prev=null,n}var i=new us(e);return this._records.set(e,i),i.currentValue=t,this._addToAdditions(i),i},e.prototype._reset=function(){if(this.isDirty){var e=void 0;for(this._previousMapHead=this._mapHead,e=this._previousMapHead;null!==e;e=e._next)e._nextPrevious=e._next;for(e=this._changesHead;null!==e;e=e._nextChanged)e.previousValue=e.currentValue;for(e=this._additionsHead;null!=e;e=e._nextAdded)e.previousValue=e.currentValue;this._changesHead=this._changesTail=null,this._additionsHead=this._additionsTail=null,this._removalsHead=null}},e.prototype._maybeAddToChanges=function(e,t){i(t,e.currentValue)||(e.previousValue=e.currentValue,e.currentValue=t,this._addToChanges(e))},e.prototype._addToAdditions=function(e){null===this._additionsHead?this._additionsHead=this._additionsTail=e:(this._additionsTail._nextAdded=e,this._additionsTail=e)},e.prototype._addToChanges=function(e){null===this._changesHead?this._changesHead=this._changesTail=e:(this._changesTail._nextChanged=e,this._changesTail=e)},e.prototype._forEach=function(e,t){e instanceof Map?e.forEach(t):Object.keys(e).forEach(function(n){return t(e[n],n)})},e}(),us=function(){function e(e){this.key=e,this.previousValue=null,this.currentValue=null,this._nextPrevious=null,this._next=null,this._prev=null,this._nextAdded=null,this._nextRemoved=null,this._nextChanged=null}return e}(),cs=function(){function e(e){this.factories=e}return e.create=function(t,n){if(null!=n){var r=n.factories.slice();return t=t.concat(r),new e(t)}return new e(t)},e.extend=function(t){return{provide:e,useFactory:function(n){if(!n)throw new Error("Cannot extend IterableDiffers without a parent injector");return e.create(t,n)},deps:[[e,new di,new ui]]}},e.prototype.find=function(e){var t=this.factories.find(function(t){return t.supports(e)});if(null!=t)return t;throw new Error("Cannot find a differ supporting object '"+e+"' of type '"+Ne(e)+"'")},e}(),ps=function(){function e(e){this.factories=e}return e.create=function(t,n){if(n){var r=n.factories.slice();t=t.concat(r)}return new e(t)},e.extend=function(t){return{provide:e,useFactory:function(n){if(!n)throw new Error("Cannot extend KeyValueDiffers without a parent injector");return e.create(t,n)},deps:[[e,new di,new ui]]}},e.prototype.find=function(e){var t=this.factories.find(function(t){return t.supports(e)});if(t)return t;throw new Error("Cannot find a differ supporting object '"+e+"'")},e}(),ds=[new ss],fs=[new ts],hs=new cs(fs),gs=new ps(ds),ms=[{provide:Ui,useValue:"unknown"},Oa,{provide:Sa,useExisting:Oa},{provide:Ei,useFactory:je,deps:[]},ya,Wi],ys=de(null,"core",ms),vs=new Io("LocaleId"),bs=new Io("Translations"),_s=new Io("TranslationsFormat"),ws={};ws.Error=0,ws.Warning=1,ws.Ignore=2,ws[ws.Error]="Error",ws[ws.Warning]="Warning",ws[ws.Ignore]="Ignore";var xs=function(){function e(e){}return e}();xs.decorators=[{type:ri,args:[{providers:[Ma,{provide:Pa,useExisting:Ma},Vi,Xi,Hi,{provide:cs,useFactory:De},{provide:ps,useFactory:Le},{provide:vs,useFactory:Fe,deps:[[new li(vs),new ui,new di]]}]}]}],xs.ctorParameters=function(){return[{type:Pa}]};var ks={};ks.NONE=0,ks.HTML=1,ks.STYLE=2,ks.SCRIPT=3,ks.URL=4,ks.RESOURCE_URL=5,ks[ks.NONE]="NONE",ks[ks.HTML]="HTML",ks[ks.STYLE]="STYLE",ks[ks.SCRIPT]="SCRIPT",ks[ks.URL]="URL",ks[ks.RESOURCE_URL]="RESOURCE_URL";var Cs=function(){function e(){}return e.prototype.sanitize=function(e,t){},e}(),Ss=function(){function e(){}return e.prototype.view=function(){},e.prototype.nodeIndex=function(){},e.prototype.injector=function(){},e.prototype.component=function(){},e.prototype.providerTokens=function(){},e.prototype.references=function(){},e.prototype.context=function(){},e.prototype.componentRenderElement=function(){},e.prototype.renderNode=function(){},e.prototype.logError=function(e){for(var t=[],n=1;n=0;t--){var n=Jt(this._data,t);Os.destroyView(n)}},e.prototype.get=function(e){var t=this._embeddedViews[e];if(t){var n=new qs(t);return n.attachToViewContainerRef(this),n}return null},Object.defineProperty(e.prototype,"length",{get:function(){return this._embeddedViews.length},enumerable:!0,configurable:!0}),e.prototype.createEmbeddedView=function(e,t,n){var r=e.createEmbeddedView(t||{});return this.insert(r,n),r},e.prototype.createComponent=function(e,t,n,r,o){var i=n||this.parentInjector;o||e instanceof aa||(o=i.get(sa));var a=e.create(i,r,void 0,o);return this.insert(a.hostView,t),a},e.prototype.insert=function(e,t){if(e.destroyed)throw new Error("Cannot insert a destroyed View in a ViewContainer!");var n=e,r=n._view;return Zt(this._view,this._data,t,r),n.attachToViewContainerRef(this),e},e.prototype.move=function(e,t){if(e.destroyed)throw new Error("Cannot move a destroyed View in a ViewContainer!");var n=this._embeddedViews.indexOf(e._view);return Qt(this._data,n,t),e},e.prototype.indexOf=function(e){return this._embeddedViews.indexOf(e._view)},e.prototype.remove=function(e){var t=Jt(this._data,e);t&&Os.destroyView(t)},e.prototype.detach=function(e){var t=Jt(this._data,e);return t?new qs(t):null},e}(),qs=function(){function e(e){this._view=e,this._viewContainerRef=null,this._appRef=null}return Object.defineProperty(e.prototype,"rootNodes",{get:function(){return yt(this._view)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"context",{get:function(){return this._view.context},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"destroyed",{get:function(){return 0!=(128&this._view.state)},enumerable:!0,configurable:!0}),e.prototype.markForCheck=function(){rt(this._view)},e.prototype.detach=function(){this._view.state&=-5},e.prototype.detectChanges=function(){var e=this._view.root.rendererFactory;e.begin&&e.begin(),Os.checkAndUpdateView(this._view),e.end&&e.end()},e.prototype.checkNoChanges=function(){Os.checkNoChangesView(this._view)},e.prototype.reattach=function(){this._view.state|=4},e.prototype.onDestroy=function(e){this._view.disposables||(this._view.disposables=[]),this._view.disposables.push(e)},e.prototype.destroy=function(){this._appRef?this._appRef.detachView(this):this._viewContainerRef&&this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)),Os.destroyView(this._view)},e.prototype.detachFromAppRef=function(){this._appRef=null,tn(this._view),Os.dirtyParentQueries(this._view)},e.prototype.attachToAppRef=function(e){if(this._viewContainerRef)throw new Error("This view is already attached to a ViewContainer!");this._appRef=e},e.prototype.attachToViewContainerRef=function(e){if(this._appRef)throw new Error("This view is already attached directly to the ApplicationRef!");this._viewContainerRef=e},e}(),Us=function(e){function t(t,n){var r=e.call(this)||this;return r._parentView=t,r._def=n,r}return So.a(t,e),t.prototype.createEmbeddedView=function(e){return new qs(Os.createEmbeddedView(this._parentView,this._def,this._def.element.template,e))},Object.defineProperty(t.prototype,"elementRef",{get:function(){return new Da(Ve(this._parentView,this._def.nodeIndex).renderElement)},enumerable:!0,configurable:!0}),t}(qa),$s=function(){function e(e,t){this.view=e,this.elDef=t}return e.prototype.get=function(e,t){void 0===t&&(t=yi.THROW_IF_NOT_FOUND);var n=!!this.elDef&&0!=(33554432&this.elDef.flags);return Os.resolveDep(this.view,this.elDef,n,{flags:0,token:e,tokenKey:Ge(e)},t)},e}(),Ys=function(){function e(e){this.delegate=e}return e.prototype.selectRootElement=function(e){return this.delegate.selectRootElement(e)},e.prototype.createElement=function(e,t){var n=kt(t),r=n[0],o=n[1],i=this.delegate.createElement(o,r);return e&&this.delegate.appendChild(e,i),i},e.prototype.createViewRoot=function(e){return e},e.prototype.createTemplateAnchor=function(e){var t=this.delegate.createComment("");return e&&this.delegate.appendChild(e,t),t},e.prototype.createText=function(e,t){var n=this.delegate.createText(t);return e&&this.delegate.appendChild(e,n),n},e.prototype.projectNodes=function(e,t){for(var n=0;n-1)return r;if(r=n.getPluralCategory(e),t.indexOf(r)>-1)return r;if(t.indexOf("other")>-1)return"other";throw new Error('No plural message found for value "'+e+'"')}function a(e,t){"string"==typeof t&&(t=parseInt(t,10));var n=t,r=n.toString().replace(/^[^.]*\.?/,""),o=Math.floor(Math.abs(n)),i=r.length,a=parseInt(r,10),s=parseInt(n.toString().replace(/^[^.]*\.?|0+$/g,""),10)||0;switch(e.split("-")[0].toLowerCase()){case"af":case"asa":case"az":case"bem":case"bez":case"bg":case"brx":case"ce":case"cgg":case"chr":case"ckb":case"ee":case"el":case"eo":case"es":case"eu":case"fo":case"fur":case"gsw":case"ha":case"haw":case"hu":case"jgo":case"jmc":case"ka":case"kk":case"kkj":case"kl":case"ks":case"ksb":case"ky":case"lb":case"lg":case"mas":case"mgo":case"ml":case"mn":case"nb":case"nd":case"ne":case"nn":case"nnh":case"nyn":case"om":case"or":case"os":case"ps":case"rm":case"rof":case"rwk":case"saq":case"seh":case"sn":case"so":case"sq":case"ta":case"te":case"teo":case"tk":case"tr":case"ug":case"uz":case"vo":case"vun":case"wae":case"xog":return 1===n?U.One:U.Other;case"ak":case"ln":case"mg":case"pa":case"ti":return n===Math.floor(n)&&n>=0&&n<=1?U.One:U.Other;case"am":case"as":case"bn":case"fa":case"gu":case"hi":case"kn":case"mr":case"zu":return 0===o||1===n?U.One:U.Other;case"ar":return 0===n?U.Zero:1===n?U.One:2===n?U.Two:n%100===Math.floor(n%100)&&n%100>=3&&n%100<=10?U.Few:n%100===Math.floor(n%100)&&n%100>=11&&n%100<=99?U.Many:U.Other;case"ast":case"ca":case"de":case"en":case"et":case"fi":case"fy":case"gl":case"it":case"nl":case"sv":case"sw":case"ur":case"yi":return 1===o&&0===i?U.One:U.Other;case"be":return n%10==1&&n%100!=11?U.One:n%10===Math.floor(n%10)&&n%10>=2&&n%10<=4&&!(n%100>=12&&n%100<=14)?U.Few:n%10==0||n%10===Math.floor(n%10)&&n%10>=5&&n%10<=9||n%100===Math.floor(n%100)&&n%100>=11&&n%100<=14?U.Many:U.Other;case"br":return n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?U.One:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?U.Two:n%10===Math.floor(n%10)&&(n%10>=3&&n%10<=4||n%10==9)&&!(n%100>=10&&n%100<=19||n%100>=70&&n%100<=79||n%100>=90&&n%100<=99)?U.Few:0!==n&&n%1e6==0?U.Many:U.Other;case"bs":case"hr":case"sr":return 0===i&&o%10==1&&o%100!=11||a%10==1&&a%100!=11?U.One:0===i&&o%10===Math.floor(o%10)&&o%10>=2&&o%10<=4&&!(o%100>=12&&o%100<=14)||a%10===Math.floor(a%10)&&a%10>=2&&a%10<=4&&!(a%100>=12&&a%100<=14)?U.Few:U.Other;case"cs":case"sk":return 1===o&&0===i?U.One:o===Math.floor(o)&&o>=2&&o<=4&&0===i?U.Few:0!==i?U.Many:U.Other;case"cy":return 0===n?U.Zero:1===n?U.One:2===n?U.Two:3===n?U.Few:6===n?U.Many:U.Other;case"da":return 1===n||0!==s&&(0===o||1===o)?U.One:U.Other;case"dsb":case"hsb":return 0===i&&o%100==1||a%100==1?U.One:0===i&&o%100==2||a%100==2?U.Two:0===i&&o%100===Math.floor(o%100)&&o%100>=3&&o%100<=4||a%100===Math.floor(a%100)&&a%100>=3&&a%100<=4?U.Few:U.Other;case"ff":case"fr":case"hy":case"kab":return 0===o||1===o?U.One:U.Other;case"fil":return 0===i&&(1===o||2===o||3===o)||0===i&&o%10!=4&&o%10!=6&&o%10!=9||0!==i&&a%10!=4&&a%10!=6&&a%10!=9?U.One:U.Other;case"ga":return 1===n?U.One:2===n?U.Two:n===Math.floor(n)&&n>=3&&n<=6?U.Few:n===Math.floor(n)&&n>=7&&n<=10?U.Many:U.Other;case"gd":return 1===n||11===n?U.One:2===n||12===n?U.Two:n===Math.floor(n)&&(n>=3&&n<=10||n>=13&&n<=19)?U.Few:U.Other;case"gv":return 0===i&&o%10==1?U.One:0===i&&o%10==2?U.Two:0!==i||o%100!=0&&o%100!=20&&o%100!=40&&o%100!=60&&o%100!=80?0!==i?U.Many:U.Other:U.Few;case"he":return 1===o&&0===i?U.One:2===o&&0===i?U.Two:0!==i||n>=0&&n<=10||n%10!=0?U.Other:U.Many;case"is":return 0===s&&o%10==1&&o%100!=11||0!==s?U.One:U.Other;case"ksh":return 0===n?U.Zero:1===n?U.One:U.Other;case"kw":case"naq":case"se":case"smn":return 1===n?U.One:2===n?U.Two:U.Other;case"lag":return 0===n?U.Zero:0!==o&&1!==o||0===n?U.Other:U.One;case"lt":return n%10!=1||n%100>=11&&n%100<=19?n%10===Math.floor(n%10)&&n%10>=2&&n%10<=9&&!(n%100>=11&&n%100<=19)?U.Few:0!==a?U.Many:U.Other:U.One;case"lv":case"prg":return n%10==0||n%100===Math.floor(n%100)&&n%100>=11&&n%100<=19||2===i&&a%100===Math.floor(a%100)&&a%100>=11&&a%100<=19?U.Zero:n%10==1&&n%100!=11||2===i&&a%10==1&&a%100!=11||2!==i&&a%10==1?U.One:U.Other;case"mk":return 0===i&&o%10==1||a%10==1?U.One:U.Other;case"mt":return 1===n?U.One:0===n||n%100===Math.floor(n%100)&&n%100>=2&&n%100<=10?U.Few:n%100===Math.floor(n%100)&&n%100>=11&&n%100<=19?U.Many:U.Other;case"pl":return 1===o&&0===i?U.One:0===i&&o%10===Math.floor(o%10)&&o%10>=2&&o%10<=4&&!(o%100>=12&&o%100<=14)?U.Few:0===i&&1!==o&&o%10===Math.floor(o%10)&&o%10>=0&&o%10<=1||0===i&&o%10===Math.floor(o%10)&&o%10>=5&&o%10<=9||0===i&&o%100===Math.floor(o%100)&&o%100>=12&&o%100<=14?U.Many:U.Other;case"pt":return n===Math.floor(n)&&n>=0&&n<=2&&2!==n?U.One:U.Other;case"ro":return 1===o&&0===i?U.One:0!==i||0===n||1!==n&&n%100===Math.floor(n%100)&&n%100>=1&&n%100<=19?U.Few:U.Other;case"ru":case"uk":return 0===i&&o%10==1&&o%100!=11?U.One:0===i&&o%10===Math.floor(o%10)&&o%10>=2&&o%10<=4&&!(o%100>=12&&o%100<=14)?U.Few:0===i&&o%10==0||0===i&&o%10===Math.floor(o%10)&&o%10>=5&&o%10<=9||0===i&&o%100===Math.floor(o%100)&&o%100>=11&&o%100<=14?U.Many:U.Other;case"shi":return 0===o||1===n?U.One:n===Math.floor(n)&&n>=2&&n<=10?U.Few:U.Other;case"si":return 0===n||1===n||0===o&&1===a?U.One:U.Other;case"sl":return 0===i&&o%100==1?U.One:0===i&&o%100==2?U.Two:0===i&&o%100===Math.floor(o%100)&&o%100>=3&&o%100<=4||0!==i?U.Few:U.Other;case"tzm":return n===Math.floor(n)&&n>=0&&n<=1||n===Math.floor(n)&&n>=11&&n<=99?U.One:U.Other;default:return U.Other}}function s(e,t){t=encodeURIComponent(t);for(var n=0,r=e.split(";");n1?"short":"narrow":"long",n}function v(e){return e.reduce(function(e,t){return Object.assign({},e,t)},{})}function b(e){return function(t,n){return f(t,n,e)}}function _(e,t,n){var r=be[e];if(r)return r(t,n);var o=e,i=we.get(o);if(!i){i=[];var a=void 0;ve.exec(e);for(var s=e;s;)a=ve.exec(s),a?(i=i.concat(a.slice(1)),s=i.pop()):(i.push(s),s=null);we.set(o,i)}return i.reduce(function(e,r){var o=_e[r];return e+(o?o(t,n):w(r))},"")}function w(e){return"''"===e?"'":e.replace(/(^'|'$)/g,"").replace(/''/g,"'")}function x(e,t,n,r,o,i,a){if(void 0===i&&(i=null),void 0===a&&(a=!1),null==n)return null;if("number"!=typeof(n="string"==typeof n&&C(n)?+n:n))throw u(e,n);var s=void 0,l=void 0,c=void 0;if(r!==me.Currency&&(s=1,l=0,c=3),o){var p=o.match(ke);if(null===p)throw new Error(o+" is not a valid digit info for number pipes");null!=p[1]&&(s=k(p[1])),null!=p[3]&&(l=k(p[3])),null!=p[5]&&(c=k(p[5]))}return ye.format(n,t,r,{minimumIntegerDigits:s,minimumFractionDigits:l,maximumFractionDigits:c,currency:i,currencyAsSymbol:a})}function k(e){var t=parseInt(e);if(isNaN(t))throw new Error("Invalid integer literal when parsing "+e);return t}function C(e){return!isNaN(e-parseFloat(e))}function S(e){return null==e||""===e}function O(e){return e instanceof Date&&!isNaN(e.valueOf())}function P(e){var t=new Date(0),n=0,r=0,o=e[8]?t.setUTCFullYear:t.setFullYear,i=e[8]?t.setUTCHours:t.setHours;e[9]&&(n=M(e[9]+e[10]),r=M(e[9]+e[11])),o.call(t,M(e[1]),M(e[2])-1,M(e[3]));var a=M(e[4]||"0")-n,s=M(e[5]||"0")-r,l=M(e[6]||"0"),u=Math.round(1e3*parseFloat("0."+(e[7]||0)));return i.call(t,a,s,l,u),t}function M(e){return parseInt(e,10)}function E(e){return e===Fe}function T(e){return e===ze}function I(e){return e===Ve}function A(e){return e===Be}Object.defineProperty(t,"__esModule",{value:!0}),n.d(t,"NgLocaleLocalization",function(){return q}),n.d(t,"NgLocalization",function(){return H}),n.d(t,"ɵparseCookieValue",function(){return s}),n.d(t,"CommonModule",function(){return je}),n.d(t,"DeprecatedI18NPipesModule",function(){return De}),n.d(t,"NgClass",function(){return $}),n.d(t,"NgFor",function(){return G}),n.d(t,"NgForOf",function(){return Z}),n.d(t,"NgForOfContext",function(){return W}),n.d(t,"NgIf",function(){return J}),n.d(t,"NgIfContext",function(){return K}),n.d(t,"NgPlural",function(){return re}),n.d(t,"NgPluralCase",function(){return oe}),n.d(t,"NgStyle",function(){return ie}),n.d(t,"NgSwitch",function(){return ee}),n.d(t,"NgSwitchCase",function(){return te}),n.d(t,"NgSwitchDefault",function(){return ne}),n.d(t,"NgTemplateOutlet",function(){return ae}),n.d(t,"NgComponentOutlet",function(){return Y}),n.d(t,"DOCUMENT",function(){return Le}),n.d(t,"AsyncPipe",function(){return de}),n.d(t,"DatePipe",function(){return Me}),n.d(t,"I18nPluralPipe",function(){return Te}),n.d(t,"I18nSelectPipe",function(){return Ie}),n.d(t,"JsonPipe",function(){return Ae}),n.d(t,"LowerCasePipe",function(){return fe}),n.d(t,"CurrencyPipe",function(){return Oe}),n.d(t,"DecimalPipe",function(){return Ce}),n.d(t,"PercentPipe",function(){return Se}),n.d(t,"SlicePipe",function(){return Re}),n.d(t,"UpperCasePipe",function(){return ge}),n.d(t,"TitleCasePipe",function(){return he}),n.d(t,"ɵPLATFORM_BROWSER_ID",function(){return Fe}),n.d(t,"ɵPLATFORM_SERVER_ID",function(){return ze}),n.d(t,"ɵPLATFORM_WORKER_APP_ID",function(){return Ve}),n.d(t,"ɵPLATFORM_WORKER_UI_ID",function(){return Be}),n.d(t,"isPlatformBrowser",function(){return E}),n.d(t,"isPlatformServer",function(){return T}),n.d(t,"isPlatformWorkerApp",function(){return I}),n.d(t,"isPlatformWorkerUi",function(){return A}),n.d(t,"VERSION",function(){return He}),n.d(t,"PlatformLocation",function(){return j}),n.d(t,"LOCATION_INITIALIZED",function(){return D}),n.d(t,"LocationStrategy",function(){return L}),n.d(t,"APP_BASE_HREF",function(){return F}),n.d(t,"HashLocationStrategy",function(){return V}),n.d(t,"PathLocationStrategy",function(){return B}),n.d(t,"Location",function(){return z}),n.d(t,"ɵa",function(){return se}),n.d(t,"ɵb",function(){return Ne});var R=n(142),N=n(1),j=function(){function e(){}return e.prototype.getBaseHrefFromDOM=function(){},e.prototype.onPopState=function(e){},e.prototype.onHashChange=function(e){},e.prototype.pathname=function(){},e.prototype.search=function(){},e.prototype.hash=function(){},e.prototype.replaceState=function(e,t,n){},e.prototype.pushState=function(e,t,n){},e.prototype.forward=function(){},e.prototype.back=function(){},e}(),D=new N.InjectionToken("Location Initialized"),L=function(){function e(){}return e.prototype.path=function(e){},e.prototype.prepareExternalUrl=function(e){},e.prototype.pushState=function(e,t,n,r){},e.prototype.replaceState=function(e,t,n,r){},e.prototype.forward=function(){},e.prototype.back=function(){},e.prototype.onPopState=function(e){},e.prototype.getBaseHref=function(){},e}(),F=new N.InjectionToken("appBaseHref"),z=function(){function e(t){var n=this;this._subject=new N.EventEmitter,this._platformStrategy=t;var r=this._platformStrategy.getBaseHref();this._baseHref=e.stripTrailingSlash(o(r)),this._platformStrategy.onPopState(function(e){n._subject.emit({url:n.path(!0),pop:!0,type:e.type})})}return e.prototype.path=function(e){return void 0===e&&(e=!1),this.normalize(this._platformStrategy.path(e))},e.prototype.isCurrentPathEqualTo=function(t,n){return void 0===n&&(n=""),this.path()==this.normalize(t+e.normalizeQueryParams(n))},e.prototype.normalize=function(t){return e.stripTrailingSlash(r(this._baseHref,o(t)))},e.prototype.prepareExternalUrl=function(e){return e&&"/"!==e[0]&&(e="/"+e),this._platformStrategy.prepareExternalUrl(e)},e.prototype.go=function(e,t){void 0===t&&(t=""),this._platformStrategy.pushState(null,"",e,t)},e.prototype.replaceState=function(e,t){void 0===t&&(t=""),this._platformStrategy.replaceState(null,"",e,t)},e.prototype.forward=function(){this._platformStrategy.forward()},e.prototype.back=function(){this._platformStrategy.back()},e.prototype.subscribe=function(e,t,n){return this._subject.subscribe({next:e,error:t,complete:n})},e.normalizeQueryParams=function(e){return e&&"?"!==e[0]?"?"+e:e},e.joinWithSlash=function(e,t){if(0==e.length)return t;if(0==t.length)return e;var n=0;return e.endsWith("/")&&n++,t.startsWith("/")&&n++,2==n?e+t.substring(1):1==n?e+t:e+"/"+t},e.stripTrailingSlash=function(e){var t=e.match(/#|\?|$/),n=t&&t.index||e.length,r=n-("/"===e[n-1]?1:0);return e.slice(0,r)+e.slice(n)},e}();z.decorators=[{type:N.Injectable}],z.ctorParameters=function(){return[{type:L}]};var V=function(e){function t(t,n){var r=e.call(this)||this;return r._platformLocation=t,r._baseHref="",null!=n&&(r._baseHref=n),r}return R.a(t,e),t.prototype.onPopState=function(e){this._platformLocation.onPopState(e),this._platformLocation.onHashChange(e)},t.prototype.getBaseHref=function(){return this._baseHref},t.prototype.path=function(e){void 0===e&&(e=!1);var t=this._platformLocation.hash;return null==t&&(t="#"),t.length>0?t.substring(1):t},t.prototype.prepareExternalUrl=function(e){var t=z.joinWithSlash(this._baseHref,e);return t.length>0?"#"+t:t},t.prototype.pushState=function(e,t,n,r){var o=this.prepareExternalUrl(n+z.normalizeQueryParams(r));0==o.length&&(o=this._platformLocation.pathname),this._platformLocation.pushState(e,t,o)},t.prototype.replaceState=function(e,t,n,r){var o=this.prepareExternalUrl(n+z.normalizeQueryParams(r));0==o.length&&(o=this._platformLocation.pathname),this._platformLocation.replaceState(e,t,o)},t.prototype.forward=function(){this._platformLocation.forward()},t.prototype.back=function(){this._platformLocation.back()},t}(L);V.decorators=[{type:N.Injectable}],V.ctorParameters=function(){return[{type:j},{type:void 0,decorators:[{type:N.Optional},{type:N.Inject,args:[F]}]}]};var B=function(e){function t(t,n){var r=e.call(this)||this;if(r._platformLocation=t,null==n&&(n=r._platformLocation.getBaseHrefFromDOM()),null==n)throw new Error("No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.");return r._baseHref=n,r}return R.a(t,e),t.prototype.onPopState=function(e){this._platformLocation.onPopState(e),this._platformLocation.onHashChange(e)},t.prototype.getBaseHref=function(){return this._baseHref},t.prototype.prepareExternalUrl=function(e){return z.joinWithSlash(this._baseHref,e)},t.prototype.path=function(e){void 0===e&&(e=!1);var t=this._platformLocation.pathname+z.normalizeQueryParams(this._platformLocation.search),n=this._platformLocation.hash;return n&&e?""+t+n:t},t.prototype.pushState=function(e,t,n,r){var o=this.prepareExternalUrl(n+z.normalizeQueryParams(r));this._platformLocation.pushState(e,t,o)},t.prototype.replaceState=function(e,t,n,r){var o=this.prepareExternalUrl(n+z.normalizeQueryParams(r));this._platformLocation.replaceState(e,t,o)},t.prototype.forward=function(){this._platformLocation.forward()},t.prototype.back=function(){this._platformLocation.back()},t}(L);B.decorators=[{type:N.Injectable}],B.ctorParameters=function(){return[{type:j},{type:void 0,decorators:[{type:N.Optional},{type:N.Inject,args:[F]}]}]};var H=function(){function e(){}return e.prototype.getPluralCategory=function(e){},e}(),q=function(e){function t(t){var n=e.call(this)||this;return n.locale=t,n}return R.a(t,e),t.prototype.getPluralCategory=function(e){switch(a(this.locale,e)){case U.Zero:return"zero";case U.One:return"one";case U.Two:return"two";case U.Few:return"few";case U.Many:return"many";default:return"other"}},t}(H);q.decorators=[{type:N.Injectable}],q.ctorParameters=function(){return[{type:void 0,decorators:[{type:N.Inject,args:[N.LOCALE_ID]}]}]};var U={};U.Zero=0,U.One=1,U.Two=2,U.Few=3,U.Many=4,U.Other=5,U[U.Zero]="Zero",U[U.One]="One",U[U.Two]="Two",U[U.Few]="Few",U[U.Many]="Many",U[U.Other]="Other";var $=function(){function e(e,t,n,r){this._iterableDiffers=e,this._keyValueDiffers=t,this._ngEl=n,this._renderer=r,this._initialClasses=[]}return Object.defineProperty(e.prototype,"klass",{set:function(e){this._applyInitialClasses(!0),this._initialClasses="string"==typeof e?e.split(/\s+/):[],this._applyInitialClasses(!1),this._applyClasses(this._rawClass,!1)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"ngClass",{set:function(e){this._cleanupClasses(this._rawClass),this._iterableDiffer=null,this._keyValueDiffer=null,this._rawClass="string"==typeof e?e.split(/\s+/):e,this._rawClass&&(Object(N["ɵisListLikeIterable"])(this._rawClass)?this._iterableDiffer=this._iterableDiffers.find(this._rawClass).create():this._keyValueDiffer=this._keyValueDiffers.find(this._rawClass).create())},enumerable:!0,configurable:!0}),e.prototype.ngDoCheck=function(){if(this._iterableDiffer){var e=this._iterableDiffer.diff(this._rawClass);e&&this._applyIterableChanges(e)}else if(this._keyValueDiffer){var t=this._keyValueDiffer.diff(this._rawClass);t&&this._applyKeyValueChanges(t)}},e.prototype._cleanupClasses=function(e){this._applyClasses(e,!0),this._applyInitialClasses(!1)},e.prototype._applyKeyValueChanges=function(e){var t=this;e.forEachAddedItem(function(e){return t._toggleClass(e.key,e.currentValue)}),e.forEachChangedItem(function(e){return t._toggleClass(e.key,e.currentValue)}),e.forEachRemovedItem(function(e){e.previousValue&&t._toggleClass(e.key,!1)})},e.prototype._applyIterableChanges=function(e){var t=this;e.forEachAddedItem(function(e){if("string"!=typeof e.item)throw new Error("NgClass can only toggle CSS classes expressed as strings, got "+Object(N["ɵstringify"])(e.item));t._toggleClass(e.item,!0)}),e.forEachRemovedItem(function(e){return t._toggleClass(e.item,!1)})},e.prototype._applyInitialClasses=function(e){var t=this;this._initialClasses.forEach(function(n){return t._toggleClass(n,!e)})},e.prototype._applyClasses=function(e,t){var n=this;e&&(Array.isArray(e)||e instanceof Set?e.forEach(function(e){return n._toggleClass(e,!t)}):Object.keys(e).forEach(function(r){null!=e[r]&&n._toggleClass(r,!t)}))},e.prototype._toggleClass=function(e,t){var n=this;(e=e.trim())&&e.split(/\s+/g).forEach(function(e){n._renderer.setElementClass(n._ngEl.nativeElement,e,!!t)})},e}();$.decorators=[{type:N.Directive,args:[{selector:"[ngClass]"}]}],$.ctorParameters=function(){return[{type:N.IterableDiffers},{type:N.KeyValueDiffers},{type:N.ElementRef},{type:N.Renderer}]},$.propDecorators={klass:[{type:N.Input,args:["class"]}],ngClass:[{type:N.Input}]};var Y=function(){function e(e){this._viewContainerRef=e,this._componentRef=null,this._moduleRef=null}return e.prototype.ngOnChanges=function(e){if(this._viewContainerRef.clear(),this._componentRef=null,this.ngComponentOutlet){var t=this.ngComponentOutletInjector||this._viewContainerRef.parentInjector;if(e.ngComponentOutletNgModuleFactory)if(this._moduleRef&&this._moduleRef.destroy(),this.ngComponentOutletNgModuleFactory){var n=t.get(N.NgModuleRef);this._moduleRef=this.ngComponentOutletNgModuleFactory.create(n.injector)}else this._moduleRef=null;var r=this._moduleRef?this._moduleRef.componentFactoryResolver:t.get(N.ComponentFactoryResolver),o=r.resolveComponentFactory(this.ngComponentOutlet);this._componentRef=this._viewContainerRef.createComponent(o,this._viewContainerRef.length,t,this.ngComponentOutletContent)}},e.prototype.ngOnDestroy=function(){this._moduleRef&&this._moduleRef.destroy()},e}();Y.decorators=[{type:N.Directive,args:[{selector:"[ngComponentOutlet]"}]}],Y.ctorParameters=function(){return[{type:N.ViewContainerRef}]},Y.propDecorators={ngComponentOutlet:[{type:N.Input}],ngComponentOutletInjector:[{type:N.Input}],ngComponentOutletContent:[{type:N.Input}],ngComponentOutletNgModuleFactory:[{type:N.Input}]};var W=function(){function e(e,t,n,r){this.$implicit=e,this.ngForOf=t,this.index=n,this.count=r}return Object.defineProperty(e.prototype,"first",{get:function(){return 0===this.index},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"last",{get:function(){return this.index===this.count-1},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"even",{get:function(){return this.index%2==0},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"odd",{get:function(){return!this.even},enumerable:!0,configurable:!0}),e}(),Z=function(){function e(e,t,n){this._viewContainer=e,this._template=t,this._differs=n,this._differ=null}return Object.defineProperty(e.prototype,"ngForTrackBy",{get:function(){return this._trackByFn},set:function(e){Object(N.isDevMode)()&&null!=e&&"function"!=typeof e&&console&&console.warn&&console.warn("trackBy must be a function, but received "+JSON.stringify(e)+". See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information."),this._trackByFn=e},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"ngForTemplate",{set:function(e){e&&(this._template=e)},enumerable:!0,configurable:!0}),e.prototype.ngOnChanges=function(e){if("ngForOf"in e){var t=e.ngForOf.currentValue;if(!this._differ&&t)try{this._differ=this._differs.find(t).create(this.ngForTrackBy)}catch(e){throw new Error("Cannot find a differ supporting object '"+t+"' of type '"+l(t)+"'. NgFor only supports binding to Iterables such as Arrays.")}}},e.prototype.ngDoCheck=function(){if(this._differ){var e=this._differ.diff(this.ngForOf);e&&this._applyChanges(e)}},e.prototype._applyChanges=function(e){var t=this,n=[];e.forEachOperation(function(e,r,o){if(null==e.previousIndex){var i=t._viewContainer.createEmbeddedView(t._template,new W(null,t.ngForOf,-1,-1),o),a=new X(e,i);n.push(a)}else if(null==o)t._viewContainer.remove(r);else{var i=t._viewContainer.get(r);t._viewContainer.move(i,o);var a=new X(e,i);n.push(a)}});for(var r=0;r/g,">")}function S(e){Ie.attributeMap(e).forEach(function(t,n){"xmlns:ns1"!==n&&0!==n.indexOf("ns1:")||Ie.removeAttribute(e,n)});for(var t=0,n=Ie.childNodesAsList(e);t0},t.prototype.tagName=function(e){return e.tagName},t.prototype.attributeMap=function(e){for(var t=new Map,n=e.attributes,r=0;r-1},t}(le);Ce.decorators=[{type:L.Injectable}],Ce.ctorParameters=function(){return[{type:void 0,decorators:[{type:L.Inject,args:[W]}]},{type:ke,decorators:[{type:L.Inject,args:[xe]}]}]};var Se=["alt","control","meta","shift"],Oe={alt:function(e){return e.altKey},control:function(e){return e.ctrlKey},meta:function(e){return e.metaKey},shift:function(e){return e.shiftKey}},Pe=function(e){function t(t){return e.call(this,t)||this}return j.a(t,e),t.prototype.supports=function(e){return null!=t.parseEventName(e)},t.prototype.addEventListener=function(e,n,o){var i=t.parseEventName(n),a=t.eventCallback(i.fullKey,o,this.manager.getZone());return this.manager.getZone().runOutsideAngular(function(){return r().onAndCancel(e,i.domEventName,a)})},t.parseEventName=function(e){var n=e.toLowerCase().split("."),r=n.shift();if(0===n.length||"keydown"!==r&&"keyup"!==r)return null;var o=t._normalizeKey(n.pop()),i="";if(Se.forEach(function(e){var t=n.indexOf(e);t>-1&&(n.splice(t,1),i+=e+".")}),i+=o,0!=n.length||0===o.length)return null;var a={};return a.domEventName=r,a.fullKey=i,a},t.getEventFullKey=function(e){var t="",n=r().getEventKey(e);return n=n.toLowerCase()," "===n?n="space":"."===n&&(n="dot"),Se.forEach(function(r){if(r!=n){(0,Oe[r])(e)&&(t+=r+".")}}),t+=n},t.eventCallback=function(e,n,r){return function(o){t.getEventFullKey(o)===e&&r.runGuarded(function(){return n(o)})}},t._normalizeKey=function(e){switch(e){case"esc":return"escape";default:return e}},t}(le);Pe.decorators=[{type:L.Injectable}],Pe.ctorParameters=function(){return[{type:void 0,decorators:[{type:L.Inject,args:[W]}]}]};var Me=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:\/?#]*(?:[\/?#]|$))/gi,Ee=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+\/]+=*$/i,Te=null,Ie=null,Ae=w("area,br,col,hr,img,wbr"),Re=w("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),Ne=w("rp,rt"),je=x(Ne,Re),De=x(Re,w("address,article,aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul")),Le=x(Ne,w("a,abbr,acronym,audio,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video")),Fe=x(Ae,De,Le,je),ze=w("background,cite,href,itemtype,longdesc,poster,src,xlink:href"),Ve=w("srcset"),Be=w("abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,scope,scrolling,shape,size,sizes,span,srclang,start,summary,tabindex,target,title,translate,type,usemap,valign,value,vspace,width"),He=x(ze,Ve,Be),qe=function(){function e(){this.sanitizedSomething=!1,this.buf=[]}return e.prototype.sanitizeChildren=function(e){for(var t=e.firstChild;t;)if(Ie.isElementNode(t)?this.startElement(t):Ie.isTextNode(t)?this.chars(Ie.nodeValue(t)):this.sanitizedSomething=!0,Ie.firstChild(t))t=Ie.firstChild(t);else for(;t;){Ie.isElementNode(t)&&this.endElement(t);var n=k(t,Ie.nextSibling(t));if(n){t=n;break}t=k(t,Ie.parentElement(t))}return this.buf.join("")},e.prototype.startElement=function(e){var t=this,n=Ie.nodeName(e).toLowerCase();if(!Fe.hasOwnProperty(n))return void(this.sanitizedSomething=!0);this.buf.push("<"),this.buf.push(n),Ie.attributeMap(e).forEach(function(e,n){var r=n.toLowerCase();if(!He.hasOwnProperty(r))return void(t.sanitizedSomething=!0);ze[r]&&(e=v(e)),Ve[r]&&(e=b(e)),t.buf.push(" "),t.buf.push(n),t.buf.push('="'),t.buf.push(C(e)),t.buf.push('"')}),this.buf.push(">")},e.prototype.endElement=function(e){var t=Ie.nodeName(e).toLowerCase();Fe.hasOwnProperty(t)&&!Ae.hasOwnProperty(t)&&(this.buf.push(""))},e.prototype.chars=function(e){this.buf.push(C(e))},e}(),Ue=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,$e=/([^\#-~ |!])/g,Ye=new RegExp("^([-,.\"'%_!# a-zA-Z0-9]+|(?:(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?|(?:rgb|hsl)a?|(?:repeating-)?(?:linear|radial)-gradient|(?:calc|attr))\\([-0-9.%, #a-zA-Z]+\\))$","g"),We=/^url\(([^)]+)\)$/,Ze=function(){function e(){}return e.prototype.sanitize=function(e,t){},e.prototype.bypassSecurityTrustHtml=function(e){},e.prototype.bypassSecurityTrustStyle=function(e){},e.prototype.bypassSecurityTrustScript=function(e){},e.prototype.bypassSecurityTrustUrl=function(e){},e.prototype.bypassSecurityTrustResourceUrl=function(e){},e}(),Xe=function(e){function t(t){var n=e.call(this)||this;return n._doc=t,n}return j.a(t,e),t.prototype.sanitize=function(e,t){if(null==t)return null;switch(e){case L.SecurityContext.NONE:return t;case L.SecurityContext.HTML:return t instanceof Je?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"HTML"),O(this._doc,String(t)));case L.SecurityContext.STYLE:return t instanceof Ke?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"Style"),M(t));case L.SecurityContext.SCRIPT:if(t instanceof Qe)return t.changingThisBreaksApplicationSecurity;throw this.checkNotSafeValue(t,"Script"),new Error("unsafe value used in a script context");case L.SecurityContext.URL:return t instanceof tt||t instanceof et?t.changingThisBreaksApplicationSecurity:(this.checkNotSafeValue(t,"URL"),v(String(t)));case L.SecurityContext.RESOURCE_URL:if(t instanceof tt)return t.changingThisBreaksApplicationSecurity;throw this.checkNotSafeValue(t,"ResourceURL"),new Error("unsafe value used in a resource URL context (see http://g.co/ng/security#xss)");default:throw new Error("Unexpected SecurityContext "+e+" (see http://g.co/ng/security#xss)")}},t.prototype.checkNotSafeValue=function(e,t){if(e instanceof Ge)throw new Error("Required a safe "+t+", got a "+e.getTypeName()+" (see http://g.co/ng/security#xss)")},t.prototype.bypassSecurityTrustHtml=function(e){return new Je(e)},t.prototype.bypassSecurityTrustStyle=function(e){return new Ke(e)},t.prototype.bypassSecurityTrustScript=function(e){return new Qe(e)},t.prototype.bypassSecurityTrustUrl=function(e){return new et(e)},t.prototype.bypassSecurityTrustResourceUrl=function(e){return new tt(e)},t}(Ze);Xe.decorators=[{type:L.Injectable}],Xe.ctorParameters=function(){return[{type:void 0,decorators:[{type:L.Inject,args:[W]}]}]};var Ge=function(){function e(e){this.changingThisBreaksApplicationSecurity=e}return e.prototype.getTypeName=function(){},e.prototype.toString=function(){return"SafeValue must use [property]=binding: "+this.changingThisBreaksApplicationSecurity+" (see http://g.co/ng/security#xss)"},e}(),Je=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return j.a(t,e),t.prototype.getTypeName=function(){return"HTML"},t}(Ge),Ke=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return j.a(t,e),t.prototype.getTypeName=function(){return"Style"},t}(Ge),Qe=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return j.a(t,e),t.prototype.getTypeName=function(){return"Script"},t}(Ge),et=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return j.a(t,e),t.prototype.getTypeName=function(){return"URL"},t}(Ge),tt=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return j.a(t,e),t.prototype.getTypeName=function(){return"ResourceURL"},t}(Ge),nt=[{provide:L.PLATFORM_ID,useValue:D["ɵPLATFORM_BROWSER_ID"]},{provide:L.PLATFORM_INITIALIZER,useValue:E,multi:!0},{provide:D.PlatformLocation,useClass:Z},{provide:W,useFactory:I,deps:[]}],rt=[{provide:L.Sanitizer,useExisting:Ze},{provide:Ze,useClass:Xe}],ot=Object(L.createPlatformFactory)(L.platformCore,"browser",nt),it=function(){function e(e){if(e)throw new Error("BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.")}return e.withServerTransition=function(t){return{ngModule:e,providers:[{provide:L.APP_ID,useValue:t.appId},{provide:G,useExisting:L.APP_ID},J]}},e}();it.decorators=[{type:L.NgModule,args:[{providers:[rt,{provide:L.ErrorHandler,useFactory:T,deps:[]},{provide:ae,useClass:_e,multi:!0},{provide:ae,useClass:Pe,multi:!0},{provide:ae,useClass:Ce,multi:!0},{provide:xe,useClass:ke},ge,{provide:L.RendererFactory2,useExisting:ge},{provide:ue,useExisting:ce},ce,L.Testability,se,ie,X,Q],exports:[D.CommonModule,L.ApplicationModule]}]}],it.ctorParameters=function(){return[{type:it,decorators:[{type:L.Optional},{type:L.SkipSelf}]}]};var at="undefined"!=typeof window&&window||{},st=function(){function e(e,t){this.msPerTick=e,this.numTicks=t}return e}(),lt=function(){function e(e){this.appRef=e.injector.get(L.ApplicationRef)}return e.prototype.timeChangeDetection=function(e){var t=e&&e.record,n=null!=at.console.profile;t&&n&&at.console.profile("Change Detection");for(var o=r().performanceNow(),i=0;i<5||r().performanceNow()-o<500;)this.appRef.tick(),i++;var a=r().performanceNow();t&&n&&at.console.profileEnd("Change Detection");var s=(a-o)/i;return at.console.log("ran "+i+" change detection cycles"),at.console.log(s.toFixed(2)+" ms per check"),new st(s,i)},e}(),ut="profiler",ct=function(){function e(){}return e.all=function(){return function(e){return!0}},e.css=function(e){return function(t){return null!=t.nativeElement&&r().elementMatches(t.nativeElement,e)}},e.directive=function(e){return function(t){return-1!==t.providerTokens.indexOf(e)}},e}(),pt=new L.Version("4.4.6")},function(e,t,n){"use strict";function r(e){return e.discriminator||e["x-extendedDiscriminator"]}Object.defineProperty(t,"__esModule",{value:!0});var o=n(1),i=n(443),a=n(41),s=n(88),l=n(91),u=n(231),c=n(29),p=n(7),d=n(73),f=function(){function e(e){this._schema={},this.spec=new l.BehaviorSubject(null),this.options=e.options}return e.prototype.load=function(e){var t=this;return new Promise(function(n,r){t.parser=new i,t.parser.bundle(e,{http:{withCredentials:!1}}).then(function(o){"string"==typeof e&&(t.specUrl=e),t.rawSpec=o,t._schema=c.snapshot(o);try{t.init(),t.spec.next(t._schema),n(t._schema)}catch(e){r(e)}},function(e){return r(e)})})},e.prototype.init=function(){var e,t=this.specUrl?s.parse(s.resolve(window.location.href,this.specUrl)):{protocol:window.location.protocol,host:window.location.host},n=this._schema.schemes;n&&n.length?"http"===(e=n[0])&&n.indexOf("https")>=0&&(e="https"):e=t.protocol?t.protocol.slice(0,-1):"http";var r=this._schema.host||t.host;this.basePath=this._schema.basePath||"",this.apiUrl=e+"://"+r+this.basePath,this.apiProtocol=e,this.apiUrl.endsWith("/")&&(this.apiUrl=this.apiUrl.substr(0,this.apiUrl.length-1)),this.preprocess()},e.prototype.preprocess=function(){var e=new u.MdRenderer;if(!this._schema.info)throw Error('Specification Error: Required field "info" is not specified at the top level of the specification');if(this._schema.info.description||(this._schema.info.description=""),this._schema.securityDefinitions&&!this.options.noAutoAuth){var t=n(148).SecurityDefinitions;e.addPreprocessor(t.insertTagIntoDescription)}this._schema.info["x-redoc-html-description"]=e.renderMd(this._schema.info.description),this._schema.info["x-redoc-markdown-headers"]=e.headings},Object.defineProperty(e.prototype,"schema",{get:function(){return this._schema},set:function(e){this._schema=e,this.spec.next(this._schema)},enumerable:!0,configurable:!0}),e.prototype.byPointer=function(e){var t=null;if(void 0==e)return null;try{t=a.JsonPointer.get(this._schema,decodeURIComponent(e))}catch(n){"#"!==e.charAt(0)&&(e="#"+e);try{t=this.parser.$refs.get(decodeURIComponent(e))}catch(e){}}return t},e.prototype.resolveRefs=function(e){var t=this;return Object.keys(e).forEach(function(n){if(e[n].$ref){var r=t.byPointer(e[n].$ref);r._pointer=e[n].$ref,e[n]=r}}),e},e.prototype.getOperationParams=function(e){function t(e,t){if(!Array.isArray(e))throw new Error("parameters must be an array. Got "+typeof e+" at "+t);return e.map(function(e,n){return e._pointer=a.JsonPointer.join(t,n),e})}"parameters"===a.JsonPointer.baseName(e)&&(e=a.JsonPointer.dirName(e));var n=a.JsonPointer.join(a.JsonPointer.dirName(e),["parameters"]),r=this.byPointer(n)||[],o=a.JsonPointer.join(e,["parameters"]),i=this.byPointer(o)||[],s={};return i.forEach(function(e){return s[e.name]=!0}),r=r.filter(function(e){return!s[e.name]}),r=t(r,n),i=t(i,o),i=this.resolveRefs(i),r=this.resolveRefs(r),i.concat(r)},e.prototype.getTagsMap=function(){for(var e=this._schema.tags||[],t={},n=0,r=e;n-1&&i.push({name:a.JsonPointer.baseName(e),$ref:e})}var l,u=r["x-extendedDiscriminator"];if(r["x-derived-from"]){l=[e].concat(r["x-derived-from"].filter(function(e){if(!e)return!1;var t=n.byPointer(e);return t&&t.discriminator}))}else l=[e];for(var c=0,p=Object.keys(o);c=0)return"break"}(b))break}if(!(m<0)){var _=void 0;if(u){for(var w=h.allOf||[],x=0,k=w;x0?o(r(e),9007199254740991):0}},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(11);t.SpecManager=r.SpecManager;var o=function(){function e(e){this.specMgr=e,this.componentSchema=null,this.dereferencedCache={}}return e.prototype.ngOnInit=function(){this.preinit()},e.prototype.preinit=function(){this.componentSchema=this.specMgr.byPointer(this.pointer||""),this.init()},e.prototype.ngOnDestroy=function(){this.destroy()},e.prototype.init=function(){},e.prototype.destroy=function(){},e}();t.BaseComponent=o;var i=function(e){function t(t,n){var r=e.call(this,t)||this;return r.specMgr=t,r.app=n,r}return __extends(t,e),t.prototype.subscribeForSearch=function(){var e=this;this.searchSubscription=this.app.searchContainingPointers.subscribe(function(t){for(var n=0;n=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function u(e){if(e>65535){e-=65536;var t=55296+(e>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}function c(e,t){var n=0;return i(v,t)?v[t]:35===t.charCodeAt(0)&&y.test(t)&&(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10),l(n))?u(n):e}function p(e){return e.indexOf("&")<0?e:e.replace(m,c)}function d(e){return w[e]}function f(e){return b.test(e)?e.replace(_,d):e}var h=Object.prototype.hasOwnProperty,g=/\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g,m=/&([a-z#][a-z0-9]{1,31});/gi,y=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,v=n(232),b=/[&<>"]/,_=/[&<>"]/g,w={"&":"&","<":"<",">":">",'"':"""};t.assign=a,t.isString=o,t.has=i,t.unescapeMd=s,t.isValidEntityCode=l,t.fromCodePoint=u,t.replaceEntities=p,t.escapeHtml=f},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(5),o=n(25),i=n(19),a=n(56)("src"),s=Function.toString,l=(""+s).split("toString");n(8).inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,n,s){var u="function"==typeof n;u&&(i(n,"name")||o(n,"name",t)),e[t]!==n&&(u&&(i(n,a)||o(n,a,e[t]?""+e[t]:l.join(String(t)))),e===r?e[t]=n:s?e[t]?e[t]=n:o(e,t,n):(delete e[t],o(e,t,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[a]||s.call(this)})},function(e,t,n){var r=n(0),o=n(3),i=n(45),a=/"/g,s=function(e,t,n,r){var o=String(i(e)),s="<"+t;return""!==n&&(s+=" "+n+'="'+String(r).replace(a,""")+'"'),s+">"+o+""};e.exports=function(e,t){var n={};n[e]=t(s),r(r.P+r.F*o(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}),"String",n)}},function(e,t,n){"use strict";(function(e){function r(){return i.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function o(e,t){if(r()=r())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+r().toString(16)+" bytes");return 0|e}function g(e){return+e!=e&&(e=0),i.alloc(+e)}function m(e,t){if(i.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return U(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return W(e).length;default:if(r)return U(e).length;t=(""+t).toLowerCase(),r=!0}}function y(e,t,n){var r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if(n>>>=0,t>>>=0,n<=t)return"";for(e||(e="utf8");;)switch(e){case"hex":return A(this,t,n);case"utf8":case"utf-8":return M(this,t,n);case"ascii":return T(this,t,n);case"latin1":case"binary":return I(this,t,n);case"base64":return P(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function v(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function b(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=i.from(t,r)),i.isBuffer(t))return 0===t.length?-1:_(e,t,n,r,o);if("number"==typeof t)return t&=255,i.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):_(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function _(e,t,n,r,o){function i(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}var a=1,s=e.length,l=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;a=2,s/=2,l/=2,n/=2}var u;if(o){var c=-1;for(u=n;us&&(n=s-l),u=n;u>=0;u--){for(var p=!0,d=0;do&&(r=o):r=o;var i=t.length;if(i%2!=0)throw new TypeError("Invalid hex string");r>i/2&&(r=i/2);for(var a=0;a239?4:i>223?3:i>191?2:1;if(o+s<=n){var l,u,c,p;switch(s){case 1:i<128&&(a=i);break;case 2:l=e[o+1],128==(192&l)&&(p=(31&i)<<6|63&l)>127&&(a=p);break;case 3:l=e[o+1],u=e[o+2],128==(192&l)&&128==(192&u)&&(p=(15&i)<<12|(63&l)<<6|63&u)>2047&&(p<55296||p>57343)&&(a=p);break;case 4:l=e[o+1],u=e[o+2],c=e[o+3],128==(192&l)&&128==(192&u)&&128==(192&c)&&(p=(15&i)<<18|(63&l)<<12|(63&u)<<6|63&c)>65535&&p<1114112&&(a=p)}}null===a?(a=65533,s=1):a>65535&&(a-=65536,r.push(a>>>10&1023|55296),a=56320|1023&a),r.push(a),o+=s}return E(r)}function E(e){var t=e.length;if(t<=Q)return String.fromCharCode.apply(String,e);for(var n="",r=0;rr)&&(n=r);for(var o="",i=t;in)throw new RangeError("Trying to access beyond buffer length")}function j(e,t,n,r,o,a){if(!i.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function D(e,t,n,r){t<0&&(t=65535+t+1);for(var o=0,i=Math.min(e.length-n,2);o>>8*(r?o:1-o)}function L(e,t,n,r){t<0&&(t=4294967295+t+1);for(var o=0,i=Math.min(e.length-n,4);o>>8*(r?o:3-o)&255}function F(e,t,n,r,o,i){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function z(e,t,n,r,o){return o||F(e,t,n,4,3.4028234663852886e38,-3.4028234663852886e38),J.write(e,t,n,r,23,4),n+4}function V(e,t,n,r,o){return o||F(e,t,n,8,1.7976931348623157e308,-1.7976931348623157e308),J.write(e,t,n,r,52,8),n+8}function B(e){if(e=H(e).replace(ee,""),e.length<2)return"";for(;e.length%4!=0;)e+="=";return e}function H(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}function q(e){return e<16?"0"+e.toString(16):e.toString(16)}function U(e,t){t=t||1/0;for(var n,r=e.length,o=null,i=[],a=0;a55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&i.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&i.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&i.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&i.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;i.push(n)}else if(n<2048){if((t-=2)<0)break;i.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function $(e){for(var t=[],n=0;n>8,o=n%256,i.push(o),i.push(r);return i}function W(e){return G.toByteArray(B(e))}function Z(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function X(e){return e!==e}var G=n(444),J=n(445),K=n(446);t.Buffer=i,t.SlowBuffer=g,t.INSPECT_MAX_BYTES=50,i.TYPED_ARRAY_SUPPORT=void 0!==e.TYPED_ARRAY_SUPPORT?e.TYPED_ARRAY_SUPPORT:function(){try{var e=new Uint8Array(1);return e.__proto__={__proto__:Uint8Array.prototype,foo:function(){return 42}},42===e.foo()&&"function"==typeof e.subarray&&0===e.subarray(1,1).byteLength}catch(e){return!1}}(),t.kMaxLength=r(),i.poolSize=8192,i._augment=function(e){return e.__proto__=i.prototype,e},i.from=function(e,t,n){return a(null,e,t,n)},i.TYPED_ARRAY_SUPPORT&&(i.prototype.__proto__=Uint8Array.prototype,i.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&i[Symbol.species]===i&&Object.defineProperty(i,Symbol.species,{value:null,configurable:!0})),i.alloc=function(e,t,n){return l(null,e,t,n)},i.allocUnsafe=function(e){return u(null,e)},i.allocUnsafeSlow=function(e){return u(null,e)},i.isBuffer=function(e){return!(null==e||!e._isBuffer)},i.compare=function(e,t){if(!i.isBuffer(e)||!i.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var n=e.length,r=t.length,o=0,a=Math.min(n,r);o0&&(e=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(e+=" ... ")),""},i.prototype.compare=function(e,t,n,r,o){if(!i.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,o>>>=0,this===e)return 0;for(var a=o-r,s=n-t,l=Math.min(a,s),u=this.slice(r,o),c=e.slice(t,n),p=0;po)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var i=!1;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return x(this,e,t,n);case"ascii":return k(this,e,t,n);case"latin1":case"binary":return C(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,t,n);default:if(i)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),i=!0}},i.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var Q=4096;i.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,e<0?(e+=n)<0&&(e=0):e>n&&(e=n),t<0?(t+=n)<0&&(t=0):t>n&&(t=n),t0&&(o*=256);)r+=this[e+--t]*o;return r},i.prototype.readUInt8=function(e,t){return t||N(e,1,this.length),this[e]},i.prototype.readUInt16LE=function(e,t){return t||N(e,2,this.length),this[e]|this[e+1]<<8},i.prototype.readUInt16BE=function(e,t){return t||N(e,2,this.length),this[e]<<8|this[e+1]},i.prototype.readUInt32LE=function(e,t){return t||N(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},i.prototype.readUInt32BE=function(e,t){return t||N(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},i.prototype.readIntLE=function(e,t,n){e|=0,t|=0,n||N(e,t,this.length);for(var r=this[e],o=1,i=0;++i=o&&(r-=Math.pow(2,8*t)),r},i.prototype.readIntBE=function(e,t,n){e|=0,t|=0,n||N(e,t,this.length);for(var r=t,o=1,i=this[e+--r];r>0&&(o*=256);)i+=this[e+--r]*o;return o*=128,i>=o&&(i-=Math.pow(2,8*t)),i},i.prototype.readInt8=function(e,t){return t||N(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},i.prototype.readInt16LE=function(e,t){t||N(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},i.prototype.readInt16BE=function(e,t){t||N(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},i.prototype.readInt32LE=function(e,t){return t||N(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},i.prototype.readInt32BE=function(e,t){return t||N(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},i.prototype.readFloatLE=function(e,t){return t||N(e,4,this.length),J.read(this,e,!0,23,4)},i.prototype.readFloatBE=function(e,t){return t||N(e,4,this.length),J.read(this,e,!1,23,4)},i.prototype.readDoubleLE=function(e,t){return t||N(e,8,this.length),J.read(this,e,!0,52,8)},i.prototype.readDoubleBE=function(e,t){return t||N(e,8,this.length),J.read(this,e,!1,52,8)},i.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t|=0,n|=0,!r){j(this,e,t,n,Math.pow(2,8*n)-1,0)}var o=1,i=0;for(this[t]=255&e;++i=0&&(i*=256);)this[t+o]=e/i&255;return t+n},i.prototype.writeUInt8=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,1,255,0),i.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},i.prototype.writeUInt16LE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,2,65535,0),i.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},i.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,2,65535,0),i.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},i.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,4,4294967295,0),i.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):L(this,e,t,!0),t+4},i.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,4,4294967295,0),i.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):L(this,e,t,!1),t+4},i.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);j(this,e,t,n,o-1,-o)}var i=0,a=1,s=0;for(this[t]=255&e;++i>0)-s&255;return t+n},i.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t|=0,!r){var o=Math.pow(2,8*n-1);j(this,e,t,n,o-1,-o)}var i=n-1,a=1,s=0;for(this[t+i]=255&e;--i>=0&&(a*=256);)e<0&&0===s&&0!==this[t+i+1]&&(s=1),this[t+i]=(e/a>>0)-s&255;return t+n},i.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,1,127,-128),i.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},i.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,2,32767,-32768),i.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):D(this,e,t,!0),t+2},i.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,2,32767,-32768),i.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):D(this,e,t,!1),t+2},i.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,4,2147483647,-2147483648),i.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):L(this,e,t,!0),t+4},i.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||j(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),i.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):L(this,e,t,!1),t+4},i.prototype.writeFloatLE=function(e,t,n){return z(this,e,t,!0,n)},i.prototype.writeFloatBE=function(e,t,n){return z(this,e,t,!1,n)},i.prototype.writeDoubleLE=function(e,t,n){return V(this,e,t,!0,n)},i.prototype.writeDoubleBE=function(e,t,n){return V(this,e,t,!1,n)},i.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t=0;--o)e[o+t]=this[o+n];else if(a<1e3||!i.TYPED_ARRAY_SUPPORT)for(o=0;o>>=0,n=void 0===n?this.length:n>>>0,e||(e=0);var a;if("number"==typeof e)for(a=t;a";return this.unstrustedSpec?n:this.sanitizer.bypassSecurityTrustHtml(n)},e=t=__decorate([r.Pipe({name:"marked"}),__metadata("design:paramtypes",[o.DomSanitizer,l.OptionsService])],e);var t}();t.MarkedPipe=d;var f=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return i.isBlank(e)?e:i.isString(e)?this.sanitizer.bypassSecurityTrustHtml(e):e},e=__decorate([r.Pipe({name:"safe"}),__metadata("design:paramtypes",[o.DomSanitizer])],e)}();t.SafePipe=f;var h={"c++":"cpp","c#":"csharp","objective-c":"objectivec",shell:"bash",viml:"vim"},g=function(){function e(e){this.sanitizer=e}return t=e,e.prototype.transform=function(e,n){if(i.isBlank(n)||0===n.length)throw new u("Prism pipe requires one argument");if(i.isBlank(e))return e;if(!i.isString(e))throw new c(t,e);var r=n[0].toString().trim().toLowerCase();h[r]&&(r=h[r]);var o=Prism.languages[r];return o||(o=Prism.languages.clike),this.sanitizer.bypassSecurityTrustHtml(Prism.highlight(e,o))},e=t=__decorate([r.Pipe({name:"prism"}),__metadata("design:paramtypes",[o.DomSanitizer])],e);var t}();t.PrismPipe=g;var m=function(){function e(){}return t=e,e.prototype.transform=function(e){if(i.isBlank(e))return e;if(!i.isString(e))throw new c(t,e);return encodeURIComponent(e)},e=t=__decorate([r.Pipe({name:"encodeURIComponent"})],e);var t}();t.EncodeURIComponentPipe=m;var y={csv:"Comma Separated",ssv:"Space Separated",tsv:"Tab Separated",pipes:"Pipe Separated"},v=function(){function e(){}return e.prototype.transform=function(e){var t=e.collectionFormat;return t||(t="csv"),"multi"===t?"Multiple "+e.in+" params of":y[t]},e=__decorate([r.Pipe({name:"collectionFormat"})],e)}();t.CollectionFormatPipe=v,t.REDOC_PIPES=[d,f,g,m,s.JsonFormatter,p,v]},function(e,t,n){var r=n(12),o=n(57);e.exports=n(14)?function(e,t,n){return r.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var r=n(45);e.exports=function(e){return Object(r(e))}},function(e,t,n){"use strict";function r(e,t){function n(){e.classList.add("ps--focus")}function r(){e.classList.remove("ps--focus")}var o=this;o.settings=l();for(var i in t)o.settings[i]=t[i];o.containerWidth=null,o.containerHeight=null,o.contentWidth=null,o.contentHeight=null,o.isRtl="rtl"===u.css(e,"direction"),o.isNegativeScroll=function(){var t=e.scrollLeft,n=null;return e.scrollLeft=-1,n=e.scrollLeft<0,e.scrollLeft=t,n}(),o.negativeScrollAdjustment=o.isNegativeScroll?e.scrollWidth-e.clientWidth:0,o.event=new c,o.ownerDocument=e.ownerDocument||document,o.scrollbarXRail=u.appendTo(u.create("div","ps__scrollbar-x-rail"),e),o.scrollbarX=u.appendTo(u.create("div","ps__scrollbar-x"),o.scrollbarXRail),o.scrollbarX.setAttribute("tabindex",0),o.event.bind(o.scrollbarX,"focus",n),o.event.bind(o.scrollbarX,"blur",r),o.scrollbarXActive=null,o.scrollbarXWidth=null,o.scrollbarXLeft=null,o.scrollbarXBottom=s.toInt(u.css(o.scrollbarXRail,"bottom")),o.isScrollbarXUsingBottom=o.scrollbarXBottom===o.scrollbarXBottom,o.scrollbarXTop=o.isScrollbarXUsingBottom?null:s.toInt(u.css(o.scrollbarXRail,"top")),o.railBorderXWidth=s.toInt(u.css(o.scrollbarXRail,"borderLeftWidth"))+s.toInt(u.css(o.scrollbarXRail,"borderRightWidth")),u.css(o.scrollbarXRail,"display","block"),o.railXMarginWidth=s.toInt(u.css(o.scrollbarXRail,"marginLeft"))+s.toInt(u.css(o.scrollbarXRail,"marginRight")),u.css(o.scrollbarXRail,"display",""),o.railXWidth=null,o.railXRatio=null,o.scrollbarYRail=u.appendTo(u.create("div","ps__scrollbar-y-rail"),e),o.scrollbarY=u.appendTo(u.create("div","ps__scrollbar-y"),o.scrollbarYRail),o.scrollbarY.setAttribute("tabindex",0),o.event.bind(o.scrollbarY,"focus",n),o.event.bind(o.scrollbarY,"blur",r),o.scrollbarYActive=null,o.scrollbarYHeight=null,o.scrollbarYTop=null,o.scrollbarYRight=s.toInt(u.css(o.scrollbarYRail,"right")),o.isScrollbarYUsingRight=o.scrollbarYRight===o.scrollbarYRight,o.scrollbarYLeft=o.isScrollbarYUsingRight?null:s.toInt(u.css(o.scrollbarYRail,"left")),o.scrollbarYOuterWidth=o.isRtl?s.outerWidth(o.scrollbarY):null,o.railBorderYWidth=s.toInt(u.css(o.scrollbarYRail,"borderTopWidth"))+s.toInt(u.css(o.scrollbarYRail,"borderBottomWidth")),u.css(o.scrollbarYRail,"display","block"),o.railYMarginHeight=s.toInt(u.css(o.scrollbarYRail,"marginTop"))+s.toInt(u.css(o.scrollbarYRail,"marginBottom")),u.css(o.scrollbarYRail,"display",""),o.railYHeight=null,o.railYRatio=null}function o(e){return e.getAttribute("data-ps-id")}function i(e,t){e.setAttribute("data-ps-id",t)}function a(e){e.removeAttribute("data-ps-id")}var s=n(51),l=n(589),u=n(65),c=n(590),p=n(591),d={};t.add=function(e,t){var n=p();return i(e,n),d[n]=new r(e,t),d[n]},t.remove=function(e){delete d[o(e)],a(e)},t.get=function(e){return d[o(e)]}},function(e,t,n){"use strict";var r=n(3);e.exports=function(e,t){return!!e&&r(function(){t?e.call(null,function(){},1):e.call(null)})}},function(e,t,n){"use strict";function r(e){return JSON.stringify(e)}function o(e){return"string"==typeof e}function i(e){return"function"==typeof e}function a(e){return void 0==e}function s(e){return e.endsWith("/")?e.substring(0,e.length-1):e}function l(e,t){return e.reduce(function(e,n){return w.call(e,n[t])?e[n[t]].push(n):e[n[t]]=[n],e},{})}function u(e,t){if(void 0===t&&(t=!1),"default"===e)return t?"error":"success";if(e<100||e>599)throw new Error("invalid HTTP code");var n="success";return e>=300&&e<400?n="redirect":e>=400?n="error":e<200&&(n="info"),n}function c(e,t){for(var n=Object.keys(t),r=-1,o=n.length;++r0||function(e){return"[object SafariRemoteNotification]"===e.toString()}(!window.safari||safari.pushNotification),t.snapshot=h,t.isJsonLike=g,t.isXmlLike=m,t.isTextLike=y,t.getJsonLikeSample=v,t.getXmlLikeSample=b,t.getTextLikeSample=_},function(e,t,n){var r=n(75),o=n(45);e.exports=function(e){return r(o(e))}},function(e,t,n){var r=n(19),o=n(26),i=n(122)("IE_PROTO"),a=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=o(e),r(e,i)?e[i]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?a:null}},function(e,t,n){var r=n(58),o=n(75),i=n(26),a=n(15),s=n(253);e.exports=function(e,t){var n=1==e,l=2==e,u=3==e,c=4==e,p=6==e,d=5==e||p,f=t||s;return function(t,s,h){for(var g,m,y=i(t),v=o(y),b=r(s,h,3),_=a(v.length),w=0,x=n?f(t,_):l?f(t,0):void 0;_>w;w++)if((d||w in v)&&(g=v[w],m=b(g,w,y),e))if(n)x[w]=m;else if(m)switch(e){case 3:return!0;case 5:return g;case 6:return w;case 2:x.push(g)}else if(c)return!1;return p?-1:u||c?c:x}}},function(e,t,n){var r=n(0),o=n(8),i=n(3);e.exports=function(e,t){var n=(o.Object||{})[e]||Object[e],a={};a[e]=t(n),r(r.S+r.F*i(function(){n(1)}),"Object",a)}},function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(e){if(c===setTimeout)return setTimeout(e,0);if((c===n||!c)&&setTimeout)return c=setTimeout,setTimeout(e,0);try{return c(e,0)}catch(t){try{return c.call(null,e,0)}catch(t){return c.call(this,e,0)}}}function i(e){if(p===clearTimeout)return clearTimeout(e);if((p===r||!p)&&clearTimeout)return p=clearTimeout,clearTimeout(e);try{return p(e)}catch(t){try{return p.call(null,e)}catch(t){return p.call(this,e)}}}function a(){g&&f&&(g=!1,f.length?h=f.concat(h):m=-1,h.length&&s())}function s(){if(!g){var e=o(a);g=!0;for(var t=h.length;t;){for(f=h,h=[];++m1)for(var n=1;n0?r:n)(e)}},function(e,t,n){var r=n(101),o=n(57),i=n(30),a=n(44),s=n(19),l=n(175),u=Object.getOwnPropertyDescriptor;t.f=n(14)?u:function(e,t){if(e=i(e),t=a(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return o(!r.f.call(e,t),e[t])}},function(e,t,n){"use strict";if(n(14)){var r=n(78),o=n(5),i=n(3),a=n(0),s=n(107),l=n(141),u=n(58),c=n(77),p=n(57),d=n(25),f=n(76),h=n(37),g=n(15),m=n(206),y=n(55),v=n(44),b=n(19),_=n(126),w=n(4),x=n(26),k=n(124),C=n(53),S=n(31),O=n(59).f,P=n(125),M=n(56),E=n(9),T=n(32),I=n(121),A=n(207),R=n(106),N=n(67),j=n(128),D=n(80),L=n(138),F=n(204),z=n(12),V=n(38),B=z.f,H=V.f,q=o.RangeError,U=o.TypeError,$=o.Uint8Array,Y=Array.prototype,W=l.ArrayBuffer,Z=l.DataView,X=T(0),G=T(2),J=T(3),K=T(4),Q=T(5),ee=T(6),te=I(!0),ne=I(!1),re=R.values,oe=R.keys,ie=R.entries,ae=Y.lastIndexOf,se=Y.reduce,le=Y.reduceRight,ue=Y.join,ce=Y.sort,pe=Y.slice,de=Y.toString,fe=Y.toLocaleString,he=E("iterator"),ge=E("toStringTag"),me=M("typed_constructor"),ye=M("def_constructor"),ve=s.CONSTR,be=s.TYPED,_e=s.VIEW,we=T(1,function(e,t){return Oe(A(e,e[ye]),t)}),xe=i(function(){return 1===new $(new Uint16Array([1]).buffer)[0]}),ke=!!$&&!!$.prototype.set&&i(function(){new $(1).set({})}),Ce=function(e,t){var n=h(e);if(n<0||n%t)throw q("Wrong offset!");return n},Se=function(e){if(w(e)&&be in e)return e;throw U(e+" is not a typed array!")},Oe=function(e,t){if(!(w(e)&&me in e))throw U("It is not a typed array constructor!");return new e(t)},Pe=function(e,t){return Me(A(e,e[ye]),t)},Me=function(e,t){for(var n=0,r=t.length,o=Oe(e,r);r>n;)o[n]=t[n++];return o},Ee=function(e,t,n){B(e,t,{get:function(){return this._d[n]}})},Te=function(e){var t,n,r,o,i,a,s=x(e),l=arguments.length,c=l>1?arguments[1]:void 0,p=void 0!==c,d=P(s);if(void 0!=d&&!k(d)){for(a=d.call(s),r=[],t=0;!(i=a.next()).done;t++)r.push(i.value);s=r}for(p&&l>2&&(c=u(c,arguments[2],2)),t=0,n=g(s.length),o=Oe(this,n);n>t;t++)o[t]=p?c(s[t],t):s[t];return o},Ie=function(){for(var e=0,t=arguments.length,n=Oe(this,t);t>e;)n[e]=arguments[e++];return n},Ae=!!$&&i(function(){fe.call(new $(1))}),Re=function(){return fe.apply(Ae?pe.call(Se(this)):Se(this),arguments)},Ne={copyWithin:function(e,t){return F.call(Se(this),e,t,arguments.length>2?arguments[2]:void 0)},every:function(e){return K(Se(this),e,arguments.length>1?arguments[1]:void 0)},fill:function(e){return L.apply(Se(this),arguments)},filter:function(e){return Pe(this,G(Se(this),e,arguments.length>1?arguments[1]:void 0))},find:function(e){return Q(Se(this),e,arguments.length>1?arguments[1]:void 0)},findIndex:function(e){return ee(Se(this),e,arguments.length>1?arguments[1]:void 0)},forEach:function(e){X(Se(this),e,arguments.length>1?arguments[1]:void 0)},indexOf:function(e){return ne(Se(this),e,arguments.length>1?arguments[1]:void 0)},includes:function(e){return te(Se(this),e,arguments.length>1?arguments[1]:void 0)},join:function(e){return ue.apply(Se(this),arguments)},lastIndexOf:function(e){return ae.apply(Se(this),arguments)},map:function(e){return we(Se(this),e,arguments.length>1?arguments[1]:void 0)},reduce:function(e){return se.apply(Se(this),arguments)},reduceRight:function(e){return le.apply(Se(this),arguments)},reverse:function(){for(var e,t=this,n=Se(t).length,r=Math.floor(n/2),o=0;o1?arguments[1]:void 0)},sort:function(e){return ce.call(Se(this),e)},subarray:function(e,t){var n=Se(this),r=n.length,o=y(e,r);return new(A(n,n[ye]))(n.buffer,n.byteOffset+o*n.BYTES_PER_ELEMENT,g((void 0===t?r:y(t,r))-o))}},je=function(e,t){return Pe(this,pe.call(Se(this),e,t))},De=function(e){Se(this);var t=Ce(arguments[1],1),n=this.length,r=x(e),o=g(r.length),i=0;if(o+t>n)throw q("Wrong length!");for(;i255?255:255&r),o.v[f](n*t+o.o,r,xe)},E=function(e,t){B(e,t,{get:function(){return P(this,t)},set:function(e){return M(this,t,e)},enumerable:!0})};b?(h=n(function(e,n,r,o){c(e,h,u,"_d");var i,a,s,l,p=0,f=0;if(w(n)){if(!(n instanceof W||"ArrayBuffer"==(l=_(n))||"SharedArrayBuffer"==l))return be in n?Me(h,n):Te.call(h,n);i=n,f=Ce(r,t);var y=n.byteLength;if(void 0===o){if(y%t)throw q("Wrong length!");if((a=y-f)<0)throw q("Wrong length!")}else if((a=g(o)*t)+f>y)throw q("Wrong length!");s=a/t}else s=m(n),a=s*t,i=new W(a);for(d(e,"_d",{b:i,o:f,l:a,e:s,v:new Z(i)});p=0?e.substr(t).toLowerCase():""},t.getHash=function(e){var t=e.indexOf("#");return t>=0?e.substr(t):"#"},t.stripHash=function(e){var t=e.indexOf("#");return t>=0&&(e=e.substr(0,t)),e},t.isHttp=function(e){var t=s.getProtocol(e);return"http"===t||"https"===t||void 0===t&&r.browser},t.isFileSystemPath=function(e){if(r.browser)return!1;var t=s.getProtocol(e);return void 0===t||"file"===t},t.fromFileSystemPath=function(e){for(var t=0;to*r?t.INVIEW_POSITION.ABOVE:o*e.getBoundingClientRect().bottom<=o*r?t.INVIEW_POSITION.BELLOW:t.INVIEW_POSITION.INVIEW},e.prototype.scrollToPos=function(e){this.$scrollParent.scrollTo?this.$scrollParent.scrollTo(0,Math.floor(e)):this.$scrollParent.scrollTop=e},e.prototype.scrollTo=function(e,t){if(void 0===t&&(t=0),e){var n=e.getBoundingClientRect(),r=this.scrollY()+n.top-this.scrollYOffset()+t+1;return this.scrollToPos(r),r}},e.prototype.saveScroll=function(){var e=this._stickElement;if(e){var t=e.offsetParent;this._savedPosition=e.offsetTop+t.offsetTop}},e.prototype.setStickElement=function(e){this._stickElement=e},e.prototype.restoreScroll=function(){var e=this._stickElement;if(e){var t=e.offsetParent,n=e.offsetTop+t.offsetTop,r=this.scrollY()+(n-this._savedPosition);this.scrollToPos(r)}},e.prototype.relativeScrollPos=function(e){return-e.getBoundingClientRect().top+this.scrollYOffset()-1},e.prototype.scrollHandler=function(e){var t=this.scrollY()-this.prevOffsetY>0;this.prevOffsetY=this.scrollY(),this.scroll.next({isScrolledDown:t,evt:e})},e.prototype.bind=function(){var e=this;this.prevOffsetY=this.scrollY(),this._cancel=o.BrowserDomAdapter.onAndCancel(this.$scrollParent,"scroll",a.throttle(function(t){e.scrollHandler(t)},100,this))},e.prototype.unbind=function(){this._cancel()},e=__decorate([r.Injectable(),__metadata("design:paramtypes",[i.OptionsService])],e)}();t.ScrollService=s},function(e,t,n){"use strict";function r(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function o(e,t){var n={width:t.railXWidth};t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft,t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-e.scrollTop:n.top=t.scrollbarXTop+e.scrollTop,a.css(t.scrollbarXRail,n);var r={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?r.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth:r.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?r.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:r.left=t.scrollbarYLeft+e.scrollLeft,a.css(t.scrollbarYRail,r),a.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),a.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}var i=n(51),a=n(65),s=n(27),l=n(52);e.exports=function(e){var t=s.get(e);t.containerWidth=e.clientWidth,t.containerHeight=e.clientHeight,t.contentWidth=e.scrollWidth,t.contentHeight=e.scrollHeight;var n;e.contains(t.scrollbarXRail)||(n=a.queryChildren(e,".ps__scrollbar-x-rail"),n.length>0&&n.forEach(function(e){a.remove(e)}),a.appendTo(t.scrollbarXRail,e)),e.contains(t.scrollbarYRail)||(n=a.queryChildren(e,".ps__scrollbar-y-rail"),n.length>0&&n.forEach(function(e){a.remove(e)}),a.appendTo(t.scrollbarYRail,e)),!t.settings.suppressScrollX&&t.containerWidth+t.settings.scrollXMarginOffset=t.railXWidth-t.scrollbarXWidth&&(t.scrollbarXLeft=t.railXWidth-t.scrollbarXWidth),t.scrollbarYTop>=t.railYHeight-t.scrollbarYHeight&&(t.scrollbarYTop=t.railYHeight-t.scrollbarYHeight),o(e,t),t.scrollbarXActive?e.classList.add("ps--active-x"):(e.classList.remove("ps--active-x"),t.scrollbarXWidth=0,t.scrollbarXLeft=0,l(e,"left",0)),t.scrollbarYActive?e.classList.add("ps--active-y"):(e.classList.remove("ps--active-y"),t.scrollbarYHeight=0,t.scrollbarYTop=0,l(e,"top",0))}},function(e,t,n){var r=n(4);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(56)("meta"),o=n(4),i=n(19),a=n(12).f,s=0,l=Object.isExtensible||function(){return!0},u=!n(3)(function(){return l(Object.preventExtensions({}))}),c=function(e){a(e,r,{value:{i:"O"+ ++s,w:{}}})},p=function(e,t){if(!o(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!i(e,r)){if(!l(e))return"F";if(!t)return"E";c(e)}return e[r].i},d=function(e,t){if(!i(e,r)){if(!l(e))return!0;if(!t)return!1;c(e)}return e[r].w},f=function(e){return u&&h.NEED&&l(e)&&!i(e,r)&&c(e),e},h=e.exports={KEY:r,NEED:!1,fastKey:p,getWeak:d,onFreeze:f}},function(e,t,n){"use strict";var r=n(84),o=n(419),i=n(212),a=n(423),s=function(){function e(e){this._isScalar=!1,e&&(this._subscribe=e)}return e.prototype.lift=function(t){var n=new e;return n.source=this,n.operator=t,n},e.prototype.subscribe=function(e,t,n){var r=this.operator,i=o.toSubscriber(e,t,n);if(r?r.call(i,this.source):i.add(this.source||!i.syncErrorThrowable?this._subscribe(i):this._trySubscribe(i)),i.syncErrorThrowable&&(i.syncErrorThrowable=!1,i.syncErrorThrown))throw i.syncErrorValue;return i},e.prototype._trySubscribe=function(e){try{return this._subscribe(e)}catch(t){e.syncErrorThrown=!0,e.syncErrorValue=t,e.error(t)}},e.prototype.forEach=function(e,t){var n=this;if(t||(r.root.Rx&&r.root.Rx.config&&r.root.Rx.config.Promise?t=r.root.Rx.config.Promise:r.root.Promise&&(t=r.root.Promise)),!t)throw new Error("no Promise impl found");return new t(function(t,r){var o;o=n.subscribe(function(t){if(o)try{e(t)}catch(e){r(e),o.unsubscribe()}else e(t)},r,t)})},e.prototype._subscribe=function(e){return this.source.subscribe(e)},e.prototype[i.observable]=function(){return this},e.prototype.pipe=function(){for(var e=[],t=0;t0&&(c=e.exports.formatter.apply(null,u)),n&&n.message&&(c+=(c?" \n":"")+n.message);var p=new t(c);return o(p,n),i(p),a(p,r),p}}function o(e,t){u(e,t),a(e,t)}function i(e){e.toJSON=s,e.inspect=l}function a(e,t){if(t&&"object"==typeof t)for(var n=Object.keys(t),r=0;r=0))try{e[o]=t[o]}catch(e){}}}function s(){var e={},t=Object.keys(this);t=t.concat(v);for(var n=0;n=0)return t.splice(n,1),t.join("\n")}return e}}function d(e){if(!b)return!1;var t=Object.getOwnPropertyDescriptor(e,"stack");return!!t&&"function"==typeof t.get}function f(e,t){var n=Object.getOwnPropertyDescriptor(e,"stack");Object.defineProperty(e,"stack",{get:function(){return c(n.get.apply(e),t.stack)},enumerable:!1,configurable:!0})}function h(e){var t=Object.getOwnPropertyDescriptor(e,"stack");Object.defineProperty(e,"stack",{get:function(){return p(t.get.apply(e))},enumerable:!1,configurable:!0})}var g=n(474),m=Array.prototype.slice,y=["name","message","stack"],v=["name","message","description","number","code","fileName","lineNumber","columnNumber","sourceURL","line","column","stack"];e.exports=r(Error),e.exports.error=r(Error),e.exports.eval=r(EvalError),e.exports.range=r(RangeError),e.exports.reference=r(ReferenceError),e.exports.syntax=r(SyntaxError),e.exports.type=r(TypeError),e.exports.uri=r(URIError),e.exports.formatter=g;var b=function(){return!(!Object.getOwnPropertyDescriptor||!Object.defineProperty||"undefined"!=typeof navigator&&/Android/.test(navigator.userAgent))}()},function(e,t,n){"use strict";function r(e){var t,n=["ps--in-scrolling"];return t=void 0===e?["ps--x","ps--y"]:["ps--"+e],n.concat(t)}var o=n(65),i=t.toInt=function(e){return parseInt(e,10)||0};t.isEditable=function(e){return o.matches(e,"input,[contenteditable]")||o.matches(e,"select,[contenteditable]")||o.matches(e,"textarea,[contenteditable]")||o.matches(e,"button,[contenteditable]")},t.removePsClasses=function(e){for(var t=0;t=i.contentHeight-i.containerHeight&&(n=i.contentHeight-i.containerHeight,n-e.scrollTop<=2?n=e.scrollTop:e.scrollTop=n,e.dispatchEvent(o("ps-y-reach-end"))),"left"===t&&n>=i.contentWidth-i.containerWidth&&(n=i.contentWidth-i.containerWidth,n-e.scrollLeft<=2?n=e.scrollLeft:e.scrollLeft=n,e.dispatchEvent(o("ps-x-reach-end"))),void 0===i.lastTop&&(i.lastTop=e.scrollTop),void 0===i.lastLeft&&(i.lastLeft=e.scrollLeft),"top"===t&&ni.lastTop&&e.dispatchEvent(o("ps-scroll-down")),"left"===t&&ni.lastLeft&&e.dispatchEvent(o("ps-scroll-right")),"top"===t&&n!==i.lastTop&&(e.scrollTop=i.lastTop=n,e.dispatchEvent(o("ps-scroll-y"))),"left"===t&&n!==i.lastLeft&&(e.scrollLeft=i.lastLeft=n,e.dispatchEvent(o("ps-scroll-x")))}},function(e,t,n){var r=n(2),o=n(177),i=n(123),a=n(122)("IE_PROTO"),s=function(){},l=function(){var e,t=n(176)("iframe"),r=i.length;for(t.style.display="none",n(179).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(" + + + diff --git a/backend/static/drf-yasg/redoc-old/redoc.min.js.map.gz b/backend/static/drf-yasg/redoc-old/redoc.min.js.map.gz new file mode 100644 index 0000000..9e051fb Binary files /dev/null and b/backend/static/drf-yasg/redoc-old/redoc.min.js.map.gz differ diff --git a/backend/static/drf-yasg/redoc/LICENSE b/backend/static/drf-yasg/redoc/LICENSE new file mode 100644 index 0000000..20075d8 --- /dev/null +++ b/backend/static/drf-yasg/redoc/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015-present, Rebilly, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/backend/static/drf-yasg/redoc/LICENSE.gz b/backend/static/drf-yasg/redoc/LICENSE.gz new file mode 100644 index 0000000..b39ba68 Binary files /dev/null and b/backend/static/drf-yasg/redoc/LICENSE.gz differ diff --git a/backend/static/drf-yasg/redoc/redoc-logo.png b/backend/static/drf-yasg/redoc/redoc-logo.png new file mode 100644 index 0000000..73e5e03 Binary files /dev/null and b/backend/static/drf-yasg/redoc/redoc-logo.png differ diff --git a/backend/static/drf-yasg/redoc/redoc.min.js b/backend/static/drf-yasg/redoc/redoc.min.js new file mode 100644 index 0000000..08bfb15 --- /dev/null +++ b/backend/static/drf-yasg/redoc/redoc.min.js @@ -0,0 +1,1806 @@ +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):"function"==typeof define&&define.amd?define(["null"],t):"object"==typeof exports?exports.Redoc=t(require("null")):e.Redoc=t(e.null)}(this,(function(e){return function(){var t={5499:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;const r=n(3325),o=n(6479),i=n(5522),a=n(1603),s=["/properties"],l="http://json-schema.org/draft-07/schema";class c extends r.default{_addVocabularies(){super._addVocabularies(),o.default.forEach((e=>this.addVocabulary(e))),this.opts.discriminator&&this.addKeyword(i.default)}_addDefaultMetaSchema(){if(super._addDefaultMetaSchema(),!this.opts.meta)return;const e=this.opts.$data?this.$dataMetaSchema(a,s):a;this.addMetaSchema(e,l,!1),this.refs["http://json-schema.org/schema"]=l}defaultMeta(){return this.opts.defaultMeta=super.defaultMeta()||(this.getSchema(l)?l:void 0)}}e.exports=t=c,Object.defineProperty(t,"__esModule",{value:!0}),t.default=c;var u=n(1321);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return u.KeywordCxt}});var p=n(4475);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return p._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return p.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return p.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return p.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return p.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return p.CodeGen}})},4667:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regexpCode=t.getProperty=t.safeStringify=t.stringify=t.strConcat=t.addCodeArg=t.str=t._=t.nil=t._Code=t.Name=t.IDENTIFIER=t._CodeOrName=void 0;class n{}t._CodeOrName=n,t.IDENTIFIER=/^[a-z$_][a-z$_0-9]*$/i;class r extends n{constructor(e){if(super(),!t.IDENTIFIER.test(e))throw new Error("CodeGen: name must be a valid identifier");this.str=e}toString(){return this.str}emptyStr(){return!1}get names(){return{[this.str]:1}}}t.Name=r;class o extends n{constructor(e){super(),this._items="string"==typeof e?[e]:e}toString(){return this.str}emptyStr(){if(this._items.length>1)return!1;const e=this._items[0];return""===e||'""'===e}get str(){var e;return null!==(e=this._str)&&void 0!==e?e:this._str=this._items.reduce(((e,t)=>`${e}${t}`),"")}get names(){var e;return null!==(e=this._names)&&void 0!==e?e:this._names=this._items.reduce(((e,t)=>(t instanceof r&&(e[t.str]=(e[t.str]||0)+1),e)),{})}}function i(e,...t){const n=[e[0]];let r=0;for(;r"),GTE:new r._Code(">="),LT:new r._Code("<"),LTE:new r._Code("<="),EQ:new r._Code("==="),NEQ:new r._Code("!=="),NOT:new r._Code("!"),OR:new r._Code("||"),AND:new r._Code("&&"),ADD:new r._Code("+")};class s{optimizeNodes(){return this}optimizeNames(e,t){return this}}class l extends s{constructor(e,t,n){super(),this.varKind=e,this.name=t,this.rhs=n}render({es5:e,_n:t}){const n=e?o.varKinds.var:this.varKind,r=void 0===this.rhs?"":` = ${this.rhs}`;return`${n} ${this.name}${r};`+t}optimizeNames(e,t){if(e[this.name.str])return this.rhs&&(this.rhs=R(this.rhs,e,t)),this}get names(){return this.rhs instanceof r._CodeOrName?this.rhs.names:{}}}class c extends s{constructor(e,t,n){super(),this.lhs=e,this.rhs=t,this.sideEffects=n}render({_n:e}){return`${this.lhs} = ${this.rhs};`+e}optimizeNames(e,t){if(!(this.lhs instanceof r.Name)||e[this.lhs.str]||this.sideEffects)return this.rhs=R(this.rhs,e,t),this}get names(){return C(this.lhs instanceof r.Name?{}:{...this.lhs.names},this.rhs)}}class u extends c{constructor(e,t,n,r){super(e,n,r),this.op=t}render({_n:e}){return`${this.lhs} ${this.op}= ${this.rhs};`+e}}class p extends s{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`${this.label}:`+e}}class d extends s{constructor(e){super(),this.label=e,this.names={}}render({_n:e}){return`break${this.label?` ${this.label}`:""};`+e}}class f extends s{constructor(e){super(),this.error=e}render({_n:e}){return`throw ${this.error};`+e}get names(){return this.error.names}}class h extends s{constructor(e){super(),this.code=e}render({_n:e}){return`${this.code};`+e}optimizeNodes(){return`${this.code}`?this:void 0}optimizeNames(e,t){return this.code=R(this.code,e,t),this}get names(){return this.code instanceof r._CodeOrName?this.code.names:{}}}class m extends s{constructor(e=[]){super(),this.nodes=e}render(e){return this.nodes.reduce(((t,n)=>t+n.render(e)),"")}optimizeNodes(){const{nodes:e}=this;let t=e.length;for(;t--;){const n=e[t].optimizeNodes();Array.isArray(n)?e.splice(t,1,...n):n?e[t]=n:e.splice(t,1)}return e.length>0?this:void 0}optimizeNames(e,t){const{nodes:n}=this;let r=n.length;for(;r--;){const o=n[r];o.optimizeNames(e,t)||(j(e,o.names),n.splice(r,1))}return n.length>0?this:void 0}get names(){return this.nodes.reduce(((e,t)=>$(e,t.names)),{})}}class g extends m{render(e){return"{"+e._n+super.render(e)+"}"+e._n}}class y extends m{}class v extends g{}v.kind="else";class b extends g{constructor(e,t){super(t),this.condition=e}render(e){let t=`if(${this.condition})`+super.render(e);return this.else&&(t+="else "+this.else.render(e)),t}optimizeNodes(){super.optimizeNodes();const e=this.condition;if(!0===e)return this.nodes;let t=this.else;if(t){const e=t.optimizeNodes();t=this.else=Array.isArray(e)?new v(e):e}return t?!1===e?t instanceof b?t:t.nodes:this.nodes.length?this:new b(T(e),t instanceof b?[t]:t.nodes):!1!==e&&this.nodes.length?this:void 0}optimizeNames(e,t){var n;if(this.else=null===(n=this.else)||void 0===n?void 0:n.optimizeNames(e,t),super.optimizeNames(e,t)||this.else)return this.condition=R(this.condition,e,t),this}get names(){const e=super.names;return C(e,this.condition),this.else&&$(e,this.else.names),e}}b.kind="if";class w extends g{}w.kind="for";class x extends w{constructor(e){super(),this.iteration=e}render(e){return`for(${this.iteration})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iteration=R(this.iteration,e,t),this}get names(){return $(super.names,this.iteration.names)}}class k extends w{constructor(e,t,n,r){super(),this.varKind=e,this.name=t,this.from=n,this.to=r}render(e){const t=e.es5?o.varKinds.var:this.varKind,{name:n,from:r,to:i}=this;return`for(${t} ${n}=${r}; ${n}<${i}; ${n}++)`+super.render(e)}get names(){const e=C(super.names,this.from);return C(e,this.to)}}class _ extends w{constructor(e,t,n,r){super(),this.loop=e,this.varKind=t,this.name=n,this.iterable=r}render(e){return`for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})`+super.render(e)}optimizeNames(e,t){if(super.optimizeNames(e,t))return this.iterable=R(this.iterable,e,t),this}get names(){return $(super.names,this.iterable.names)}}class O extends g{constructor(e,t,n){super(),this.name=e,this.args=t,this.async=n}render(e){return`${this.async?"async ":""}function ${this.name}(${this.args})`+super.render(e)}}O.kind="func";class S extends m{render(e){return"return "+super.render(e)}}S.kind="return";class E extends g{render(e){let t="try"+super.render(e);return this.catch&&(t+=this.catch.render(e)),this.finally&&(t+=this.finally.render(e)),t}optimizeNodes(){var e,t;return super.optimizeNodes(),null===(e=this.catch)||void 0===e||e.optimizeNodes(),null===(t=this.finally)||void 0===t||t.optimizeNodes(),this}optimizeNames(e,t){var n,r;return super.optimizeNames(e,t),null===(n=this.catch)||void 0===n||n.optimizeNames(e,t),null===(r=this.finally)||void 0===r||r.optimizeNames(e,t),this}get names(){const e=super.names;return this.catch&&$(e,this.catch.names),this.finally&&$(e,this.finally.names),e}}class P extends g{constructor(e){super(),this.error=e}render(e){return`catch(${this.error})`+super.render(e)}}P.kind="catch";class A extends g{render(e){return"finally"+super.render(e)}}function $(e,t){for(const n in t)e[n]=(e[n]||0)+(t[n]||0);return e}function C(e,t){return t instanceof r._CodeOrName?$(e,t.names):e}function R(e,t,n){return e instanceof r.Name?i(e):(o=e)instanceof r._Code&&o._items.some((e=>e instanceof r.Name&&1===t[e.str]&&void 0!==n[e.str]))?new r._Code(e._items.reduce(((e,t)=>(t instanceof r.Name&&(t=i(t)),t instanceof r._Code?e.push(...t._items):e.push(t),e)),[])):e;var o;function i(e){const r=n[e.str];return void 0===r||1!==t[e.str]?e:(delete t[e.str],r)}}function j(e,t){for(const n in t)e[n]=(e[n]||0)-(t[n]||0)}function T(e){return"boolean"==typeof e||"number"==typeof e||null===e?!e:r._`!${L(e)}`}A.kind="finally",t.CodeGen=class{constructor(e,t={}){this._values={},this._blockStarts=[],this._constants={},this.opts={...t,_n:t.lines?"\n":""},this._extScope=e,this._scope=new o.Scope({parent:e}),this._nodes=[new y]}toString(){return this._root.render(this.opts)}name(e){return this._scope.name(e)}scopeName(e){return this._extScope.name(e)}scopeValue(e,t){const n=this._extScope.value(e,t);return(this._values[n.prefix]||(this._values[n.prefix]=new Set)).add(n),n}getScopeValue(e,t){return this._extScope.getValue(e,t)}scopeRefs(e){return this._extScope.scopeRefs(e,this._values)}scopeCode(){return this._extScope.scopeCode(this._values)}_def(e,t,n,r){const o=this._scope.toName(t);return void 0!==n&&r&&(this._constants[o.str]=n),this._leafNode(new l(e,o,n)),o}const(e,t,n){return this._def(o.varKinds.const,e,t,n)}let(e,t,n){return this._def(o.varKinds.let,e,t,n)}var(e,t,n){return this._def(o.varKinds.var,e,t,n)}assign(e,t,n){return this._leafNode(new c(e,t,n))}add(e,n){return this._leafNode(new u(e,t.operators.ADD,n))}code(e){return"function"==typeof e?e():e!==r.nil&&this._leafNode(new h(e)),this}object(...e){const t=["{"];for(const[n,o]of e)t.length>1&&t.push(","),t.push(n),(n!==o||this.opts.es5)&&(t.push(":"),r.addCodeArg(t,o));return t.push("}"),new r._Code(t)}if(e,t,n){if(this._blockNode(new b(e)),t&&n)this.code(t).else().code(n).endIf();else if(t)this.code(t).endIf();else if(n)throw new Error('CodeGen: "else" body without "then" body');return this}elseIf(e){return this._elseNode(new b(e))}else(){return this._elseNode(new v)}endIf(){return this._endBlockNode(b,v)}_for(e,t){return this._blockNode(e),t&&this.code(t).endFor(),this}for(e,t){return this._for(new x(e),t)}forRange(e,t,n,r,i=(this.opts.es5?o.varKinds.var:o.varKinds.let)){const a=this._scope.toName(e);return this._for(new k(i,a,t,n),(()=>r(a)))}forOf(e,t,n,i=o.varKinds.const){const a=this._scope.toName(e);if(this.opts.es5){const e=t instanceof r.Name?t:this.var("_arr",t);return this.forRange("_i",0,r._`${e}.length`,(t=>{this.var(a,r._`${e}[${t}]`),n(a)}))}return this._for(new _("of",i,a,t),(()=>n(a)))}forIn(e,t,n,i=(this.opts.es5?o.varKinds.var:o.varKinds.const)){if(this.opts.ownProperties)return this.forOf(e,r._`Object.keys(${t})`,n);const a=this._scope.toName(e);return this._for(new _("in",i,a,t),(()=>n(a)))}endFor(){return this._endBlockNode(w)}label(e){return this._leafNode(new p(e))}break(e){return this._leafNode(new d(e))}return(e){const t=new S;if(this._blockNode(t),this.code(e),1!==t.nodes.length)throw new Error('CodeGen: "return" should have one node');return this._endBlockNode(S)}try(e,t,n){if(!t&&!n)throw new Error('CodeGen: "try" without "catch" and "finally"');const r=new E;if(this._blockNode(r),this.code(e),t){const e=this.name("e");this._currNode=r.catch=new P(e),t(e)}return n&&(this._currNode=r.finally=new A,this.code(n)),this._endBlockNode(P,A)}throw(e){return this._leafNode(new f(e))}block(e,t){return this._blockStarts.push(this._nodes.length),e&&this.code(e).endBlock(t),this}endBlock(e){const t=this._blockStarts.pop();if(void 0===t)throw new Error("CodeGen: not in self-balancing block");const n=this._nodes.length-t;if(n<0||void 0!==e&&n!==e)throw new Error(`CodeGen: wrong number of nodes: ${n} vs ${e} expected`);return this._nodes.length=t,this}func(e,t=r.nil,n,o){return this._blockNode(new O(e,t,n)),o&&this.code(o).endFunc(),this}endFunc(){return this._endBlockNode(O)}optimize(e=1){for(;e-- >0;)this._root.optimizeNodes(),this._root.optimizeNames(this._root.names,this._constants)}_leafNode(e){return this._currNode.nodes.push(e),this}_blockNode(e){this._currNode.nodes.push(e),this._nodes.push(e)}_endBlockNode(e,t){const n=this._currNode;if(n instanceof e||t&&n instanceof t)return this._nodes.pop(),this;throw new Error(`CodeGen: not in block "${t?`${e.kind}/${t.kind}`:e.kind}"`)}_elseNode(e){const t=this._currNode;if(!(t instanceof b))throw new Error('CodeGen: "else" without "if"');return this._currNode=t.else=e,this}get _root(){return this._nodes[0]}get _currNode(){const e=this._nodes;return e[e.length-1]}set _currNode(e){const t=this._nodes;t[t.length-1]=e}},t.not=T;const I=D(t.operators.AND);t.and=function(...e){return e.reduce(I)};const N=D(t.operators.OR);function D(e){return(t,n)=>t===r.nil?n:n===r.nil?t:r._`${L(t)} ${e} ${L(n)}`}function L(e){return e instanceof r.Name?e:r._`(${e})`}t.or=function(...e){return e.reduce(N)}},7791:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ValueScope=t.ValueScopeName=t.Scope=t.varKinds=t.UsedValueState=void 0;const r=n(4667);class o extends Error{constructor(e){super(`CodeGen: "code" for ${e} not defined`),this.value=e.value}}var i;!function(e){e[e.Started=0]="Started",e[e.Completed=1]="Completed"}(i=t.UsedValueState||(t.UsedValueState={})),t.varKinds={const:new r.Name("const"),let:new r.Name("let"),var:new r.Name("var")};class a{constructor({prefixes:e,parent:t}={}){this._names={},this._prefixes=e,this._parent=t}toName(e){return e instanceof r.Name?e:this.name(e)}name(e){return new r.Name(this._newName(e))}_newName(e){return`${e}${(this._names[e]||this._nameGroup(e)).index++}`}_nameGroup(e){var t,n;if((null===(n=null===(t=this._parent)||void 0===t?void 0:t._prefixes)||void 0===n?void 0:n.has(e))||this._prefixes&&!this._prefixes.has(e))throw new Error(`CodeGen: prefix "${e}" is not allowed in this scope`);return this._names[e]={prefix:e,index:0}}}t.Scope=a;class s extends r.Name{constructor(e,t){super(t),this.prefix=e}setValue(e,{property:t,itemIndex:n}){this.value=e,this.scopePath=r._`.${new r.Name(t)}[${n}]`}}t.ValueScopeName=s;const l=r._`\n`;t.ValueScope=class extends a{constructor(e){super(e),this._values={},this._scope=e.scope,this.opts={...e,_n:e.lines?l:r.nil}}get(){return this._scope}name(e){return new s(e,this._newName(e))}value(e,t){var n;if(void 0===t.ref)throw new Error("CodeGen: ref must be passed in value");const r=this.toName(e),{prefix:o}=r,i=null!==(n=t.key)&&void 0!==n?n:t.ref;let a=this._values[o];if(a){const e=a.get(i);if(e)return e}else a=this._values[o]=new Map;a.set(i,r);const s=this._scope[o]||(this._scope[o]=[]),l=s.length;return s[l]=t.ref,r.setValue(t,{property:o,itemIndex:l}),r}getValue(e,t){const n=this._values[e];if(n)return n.get(t)}scopeRefs(e,t=this._values){return this._reduceValues(t,(t=>{if(void 0===t.scopePath)throw new Error(`CodeGen: name "${t}" has no value`);return r._`${e}${t.scopePath}`}))}scopeCode(e=this._values,t,n){return this._reduceValues(e,(e=>{if(void 0===e.value)throw new Error(`CodeGen: name "${e}" has no value`);return e.value.code}),t,n)}_reduceValues(e,n,a={},s){let l=r.nil;for(const c in e){const u=e[c];if(!u)continue;const p=a[c]=a[c]||new Map;u.forEach((e=>{if(p.has(e))return;p.set(e,i.Started);let a=n(e);if(a){const n=this.opts.es5?t.varKinds.var:t.varKinds.const;l=r._`${l}${n} ${e} = ${a};${this.opts._n}`}else{if(!(a=null==s?void 0:s(e)))throw new o(e);l=r._`${l}${a}${this.opts._n}`}p.set(e,i.Completed)}))}return l}}},1885:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendErrors=t.resetErrorsCount=t.reportExtraError=t.reportError=t.keyword$DataError=t.keywordError=void 0;const r=n(4475),o=n(6124),i=n(5018);function a(e,t){const n=e.const("err",t);e.if(r._`${i.default.vErrors} === null`,(()=>e.assign(i.default.vErrors,r._`[${n}]`)),r._`${i.default.vErrors}.push(${n})`),e.code(r._`${i.default.errors}++`)}function s(e,t){const{gen:n,validateName:o,schemaEnv:i}=e;i.$async?n.throw(r._`new ${e.ValidationError}(${t})`):(n.assign(r._`${o}.errors`,t),n.return(!1))}t.keywordError={message:({keyword:e})=>r.str`should pass "${e}" keyword validation`},t.keyword$DataError={message:({keyword:e,schemaType:t})=>t?r.str`"${e}" keyword must be ${t} ($data)`:r.str`"${e}" keyword is invalid ($data)`},t.reportError=function(e,n=t.keywordError,o,i){const{it:l}=e,{gen:u,compositeRule:p,allErrors:d}=l,f=c(e,n,o);(null!=i?i:p||d)?a(u,f):s(l,r._`[${f}]`)},t.reportExtraError=function(e,n=t.keywordError,r){const{it:o}=e,{gen:l,compositeRule:u,allErrors:p}=o;a(l,c(e,n,r)),u||p||s(o,i.default.vErrors)},t.resetErrorsCount=function(e,t){e.assign(i.default.errors,t),e.if(r._`${i.default.vErrors} !== null`,(()=>e.if(t,(()=>e.assign(r._`${i.default.vErrors}.length`,t)),(()=>e.assign(i.default.vErrors,null)))))},t.extendErrors=function({gen:e,keyword:t,schemaValue:n,data:o,errsCount:a,it:s}){if(void 0===a)throw new Error("ajv implementation error");const l=e.name("err");e.forRange("i",a,i.default.errors,(a=>{e.const(l,r._`${i.default.vErrors}[${a}]`),e.if(r._`${l}.instancePath === undefined`,(()=>e.assign(r._`${l}.instancePath`,r.strConcat(i.default.instancePath,s.errorPath)))),e.assign(r._`${l}.schemaPath`,r.str`${s.errSchemaPath}/${t}`),s.opts.verbose&&(e.assign(r._`${l}.schema`,n),e.assign(r._`${l}.data`,o))}))};const l={keyword:new r.Name("keyword"),schemaPath:new r.Name("schemaPath"),params:new r.Name("params"),propertyName:new r.Name("propertyName"),message:new r.Name("message"),schema:new r.Name("schema"),parentSchema:new r.Name("parentSchema")};function c(e,t,n){const{createErrors:o}=e.it;return!1===o?r._`{}`:function(e,t,n={}){const{gen:o,it:a}=e,s=[u(a,n),p(e,n)];return function(e,{params:t,message:n},o){const{keyword:a,data:s,schemaValue:c,it:u}=e,{opts:p,propertyName:d,topSchemaRef:f,schemaPath:h}=u;o.push([l.keyword,a],[l.params,"function"==typeof t?t(e):t||r._`{}`]),p.messages&&o.push([l.message,"function"==typeof n?n(e):n]),p.verbose&&o.push([l.schema,c],[l.parentSchema,r._`${f}${h}`],[i.default.data,s]),d&&o.push([l.propertyName,d])}(e,t,s),o.object(...s)}(e,t,n)}function u({errorPath:e},{instancePath:t}){const n=t?r.str`${e}${o.getErrorPath(t,o.Type.Str)}`:e;return[i.default.instancePath,r.strConcat(i.default.instancePath,n)]}function p({keyword:e,it:{errSchemaPath:t}},{schemaPath:n,parentSchema:i}){let a=i?t:r.str`${t}/${e}`;return n&&(a=r.str`${a}${o.getErrorPath(n,o.Type.Str)}`),[l.schemaPath,a]}},7805:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resolveSchema=t.getCompilingSchema=t.resolveRef=t.compileSchema=t.SchemaEnv=void 0;const r=n(4475),o=n(8451),i=n(5018),a=n(9826),s=n(6124),l=n(1321),c=n(540);class u{constructor(e){var t;let n;this.refs={},this.dynamicAnchors={},"object"==typeof e.schema&&(n=e.schema),this.schema=e.schema,this.schemaId=e.schemaId,this.root=e.root||this,this.baseId=null!==(t=e.baseId)&&void 0!==t?t:a.normalizeId(null==n?void 0:n[e.schemaId||"$id"]),this.schemaPath=e.schemaPath,this.localRefs=e.localRefs,this.meta=e.meta,this.$async=null==n?void 0:n.$async,this.refs={}}}function p(e){const t=f.call(this,e);if(t)return t;const n=a.getFullPath(e.root.baseId),{es5:s,lines:c}=this.opts.code,{ownProperties:u}=this.opts,p=new r.CodeGen(this.scope,{es5:s,lines:c,ownProperties:u});let d;e.$async&&(d=p.scopeValue("Error",{ref:o.default,code:r._`require("ajv/dist/runtime/validation_error").default`}));const h=p.scopeName("validate");e.validateName=h;const m={gen:p,allErrors:this.opts.allErrors,data:i.default.data,parentData:i.default.parentData,parentDataProperty:i.default.parentDataProperty,dataNames:[i.default.data],dataPathArr:[r.nil],dataLevel:0,dataTypes:[],definedProperties:new Set,topSchemaRef:p.scopeValue("schema",!0===this.opts.code.source?{ref:e.schema,code:r.stringify(e.schema)}:{ref:e.schema}),validateName:h,ValidationError:d,schema:e.schema,schemaEnv:e,rootId:n,baseId:e.baseId||n,schemaPath:r.nil,errSchemaPath:e.schemaPath||(this.opts.jtd?"":"#"),errorPath:r._`""`,opts:this.opts,self:this};let g;try{this._compilations.add(e),l.validateFunctionCode(m),p.optimize(this.opts.code.optimize);const t=p.toString();g=`const visitedNodesForRef = new WeakMap(); ${p.scopeRefs(i.default.scope)}return ${t}`,this.opts.code.process&&(g=this.opts.code.process(g,e));const n=new Function(`${i.default.self}`,`${i.default.scope}`,g)(this,this.scope.get());if(this.scope.value(h,{ref:n}),n.errors=null,n.schema=e.schema,n.schemaEnv=e,e.$async&&(n.$async=!0),!0===this.opts.code.source&&(n.source={validateName:h,validateCode:t,scopeValues:p._values}),this.opts.unevaluated){const{props:e,items:t}=m;n.evaluated={props:e instanceof r.Name?void 0:e,items:t instanceof r.Name?void 0:t,dynamicProps:e instanceof r.Name,dynamicItems:t instanceof r.Name},n.source&&(n.source.evaluated=r.stringify(n.evaluated))}return e.validate=n,e}catch(t){throw delete e.validate,delete e.validateName,g&&this.logger.error("Error compiling schema, function code:",g),t}finally{this._compilations.delete(e)}}function d(e){return a.inlineRef(e.schema,this.opts.inlineRefs)?e.schema:e.validate?e:p.call(this,e)}function f(e){for(const r of this._compilations)if(n=e,(t=r).schema===n.schema&&t.root===n.root&&t.baseId===n.baseId)return r;var t,n}function h(e,t){let n;for(;"string"==typeof(n=this.refs[t]);)t=n;return n||this.schemas[t]||m.call(this,e,t)}function m(e,t){const n=c.parse(t),r=a._getFullPath(n);let o=a.getFullPath(e.baseId);if(Object.keys(e.schema).length>0&&r===o)return y.call(this,n,e);const i=a.normalizeId(r),s=this.refs[i]||this.schemas[i];if("string"==typeof s){const t=m.call(this,e,s);if("object"!=typeof(null==t?void 0:t.schema))return;return y.call(this,n,t)}if("object"==typeof(null==s?void 0:s.schema)){if(s.validate||p.call(this,s),i===a.normalizeId(t)){const{schema:t}=s,{schemaId:n}=this.opts,r=t[n];return r&&(o=a.resolveUrl(o,r)),new u({schema:t,schemaId:n,root:e,baseId:o})}return y.call(this,n,s)}}t.SchemaEnv=u,t.compileSchema=p,t.resolveRef=function(e,t,n){var r;const o=a.resolveUrl(t,n),i=e.refs[o];if(i)return i;let s=h.call(this,e,o);if(void 0===s){const n=null===(r=e.localRefs)||void 0===r?void 0:r[o],{schemaId:i}=this.opts;n&&(s=new u({schema:n,schemaId:i,root:e,baseId:t}))}if(void 0===s&&this.opts.loadSchemaSync){const r=this.opts.loadSchemaSync(t,n,o);!r||this.refs[o]||this.schemas[o]||(this.addSchema(r,o,void 0),s=h.call(this,e,o))}return void 0!==s?e.refs[o]=d.call(this,s):void 0},t.getCompilingSchema=f,t.resolveSchema=m;const g=new Set(["properties","patternProperties","enum","dependencies","definitions"]);function y(e,{baseId:t,schema:n,root:r}){var o;if("/"!==(null===(o=e.fragment)||void 0===o?void 0:o[0]))return;for(const r of e.fragment.slice(1).split("/")){if("boolean"==typeof n)return;if(void 0===(n=n[s.unescapeFragment(r)]))return;const e="object"==typeof n&&n[this.opts.schemaId];!g.has(r)&&e&&(t=a.resolveUrl(t,e))}let i;if("boolean"!=typeof n&&n.$ref&&!s.schemaHasRulesButRef(n,this.RULES)){const e=a.resolveUrl(t,n.$ref);i=m.call(this,r,e)}const{schemaId:l}=this.opts;return i=i||new u({schema:n,schemaId:l,root:r,baseId:t}),i.schema!==i.root.schema?i:void 0}},5018:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={data:new r.Name("data"),valCxt:new r.Name("valCxt"),instancePath:new r.Name("instancePath"),parentData:new r.Name("parentData"),parentDataProperty:new r.Name("parentDataProperty"),rootData:new r.Name("rootData"),dynamicAnchors:new r.Name("dynamicAnchors"),vErrors:new r.Name("vErrors"),errors:new r.Name("errors"),this:new r.Name("this"),self:new r.Name("self"),scope:new r.Name("scope"),json:new r.Name("json"),jsonPos:new r.Name("jsonPos"),jsonLen:new r.Name("jsonLen"),jsonPart:new r.Name("jsonPart")};t.default=o},4143:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(9826);class o extends Error{constructor(e,t,n){super(n||`can't resolve reference ${t} from id ${e}`),this.missingRef=r.resolveUrl(e,t),this.missingSchema=r.normalizeId(r.getFullPath(this.missingRef))}}t.default=o},9826:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getSchemaRefs=t.resolveUrl=t.normalizeId=t._getFullPath=t.getFullPath=t.inlineRef=void 0;const r=n(6124),o=n(4063),i=n(4029),a=n(540),s=new Set(["type","format","pattern","maxLength","minLength","maxProperties","minProperties","maxItems","minItems","maximum","minimum","uniqueItems","multipleOf","required","enum","const"]);t.inlineRef=function(e,t=!0){return"boolean"==typeof e||(!0===t?!c(e):!!t&&u(e)<=t)};const l=new Set(["$ref","$recursiveRef","$recursiveAnchor","$dynamicRef","$dynamicAnchor"]);function c(e){for(const t in e){if(l.has(t))return!0;const n=e[t];if(Array.isArray(n)&&n.some(c))return!0;if("object"==typeof n&&c(n))return!0}return!1}function u(e){let t=0;for(const n in e){if("$ref"===n)return 1/0;if(t++,!s.has(n)&&("object"==typeof e[n]&&r.eachItem(e[n],(e=>t+=u(e))),t===1/0))return 1/0}return t}function p(e="",t){return!1!==t&&(e=h(e)),d(a.parse(e))}function d(e){return a.serialize(e).split("#")[0]+"#"}t.getFullPath=p,t._getFullPath=d;const f=/#\/?$/;function h(e){return e?e.replace(f,""):""}t.normalizeId=h,t.resolveUrl=function(e,t){return t=h(t),a.resolve(e,t)};const m=/^[a-z_][-a-z0-9._]*$/i;t.getSchemaRefs=function(e){if("boolean"==typeof e)return{};const{schemaId:t}=this.opts,n=h(e[t]),r={"":n},s=p(n,!1),l={},c=new Set;return i(e,{allKeys:!0},((e,n,o,i)=>{if(void 0===i)return;const p=s+n;let f=r[i];function g(t){if(t=h(f?a.resolve(f,t):t),c.has(t))throw d(t);c.add(t);let n=this.refs[t];return"string"==typeof n&&(n=this.refs[n]),"object"==typeof n?u(e,n.schema,t):t!==h(p)&&("#"===t[0]?(u(e,l[t],t),l[t]=e):this.refs[t]=p),t}function y(e){if("string"==typeof e){if(!m.test(e))throw new Error(`invalid anchor "${e}"`);g.call(this,`#${e}`)}}"string"==typeof e[t]&&(f=g.call(this,e[t])),y.call(this,e.$anchor),y.call(this,e.$dynamicAnchor),r[n]=f})),l;function u(e,t,n){if(void 0!==t&&!o(e,t))throw d(n)}function d(e){return new Error(`reference "${e}" resolves to more than one schema`)}}},3664:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRules=t.isJSONType=void 0;const n=new Set(["string","number","integer","boolean","null","object","array"]);t.isJSONType=function(e){return"string"==typeof e&&n.has(e)},t.getRules=function(){const e={number:{type:"number",rules:[]},string:{type:"string",rules:[]},array:{type:"array",rules:[]},object:{type:"object",rules:[]}};return{types:{...e,integer:!0,boolean:!0,null:!0},rules:[{rules:[]},e.number,e.string,e.array,e.object],post:{rules:[]},all:{},keywords:{}}}},6124:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.checkStrictMode=t.getErrorPath=t.Type=t.useFunc=t.setEvaluated=t.evaluatedPropsToName=t.mergeEvaluated=t.eachItem=t.unescapeJsonPointer=t.escapeJsonPointer=t.escapeFragment=t.unescapeFragment=t.schemaRefOrVal=t.schemaHasRulesButRef=t.schemaHasRules=t.checkUnknownRules=t.alwaysValidSchema=t.toHash=void 0;const r=n(4475),o=n(4667);function i(e,t=e.schema){const{opts:n,self:r}=e;if(!n.strictSchema)return;if("boolean"==typeof t)return;const o=r.RULES.keywords;for(const n in t)o[n]||h(e,`unknown keyword: "${n}"`)}function a(e,t){if("boolean"==typeof e)return!e;for(const n in e)if(t[n])return!0;return!1}function s(e){return"number"==typeof e?`${e}`:e.replace(/~/g,"~0").replace(/\//g,"~1")}function l(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function c({mergeNames:e,mergeToName:t,mergeValues:n,resultToName:o}){return(i,a,s,l)=>{const c=void 0===s?a:s instanceof r.Name?(a instanceof r.Name?e(i,a,s):t(i,a,s),s):a instanceof r.Name?(t(i,s,a),a):n(a,s);return l!==r.Name||c instanceof r.Name?c:o(i,c)}}function u(e,t){if(!0===t)return e.var("props",!0);const n=e.var("props",r._`{}`);return void 0!==t&&p(e,n,t),n}function p(e,t,n){Object.keys(n).forEach((n=>e.assign(r._`${t}${r.getProperty(n)}`,!0)))}t.toHash=function(e){const t={};for(const n of e)t[n]=!0;return t},t.alwaysValidSchema=function(e,t){return"boolean"==typeof t?t:0===Object.keys(t).length||(i(e,t),!a(t,e.self.RULES.all))},t.checkUnknownRules=i,t.schemaHasRules=a,t.schemaHasRulesButRef=function(e,t){if("boolean"==typeof e)return!e;for(const n in e)if("$ref"!==n&&t.all[n])return!0;return!1},t.schemaRefOrVal=function({topSchemaRef:e,schemaPath:t},n,o,i){if(!i){if("number"==typeof n||"boolean"==typeof n)return n;if("string"==typeof n)return r._`${n}`}return r._`${e}${t}${r.getProperty(o)}`},t.unescapeFragment=function(e){return l(decodeURIComponent(e))},t.escapeFragment=function(e){return encodeURIComponent(s(e))},t.escapeJsonPointer=s,t.unescapeJsonPointer=l,t.eachItem=function(e,t){if(Array.isArray(e))for(const n of e)t(n);else t(e)},t.mergeEvaluated={props:c({mergeNames:(e,t,n)=>e.if(r._`${n} !== true && ${t} !== undefined`,(()=>{e.if(r._`${t} === true`,(()=>e.assign(n,!0)),(()=>e.assign(n,r._`${n} || {}`).code(r._`Object.assign(${n}, ${t})`)))})),mergeToName:(e,t,n)=>e.if(r._`${n} !== true`,(()=>{!0===t?e.assign(n,!0):(e.assign(n,r._`${n} || {}`),p(e,n,t))})),mergeValues:(e,t)=>!0===e||{...e,...t},resultToName:u}),items:c({mergeNames:(e,t,n)=>e.if(r._`${n} !== true && ${t} !== undefined`,(()=>e.assign(n,r._`${t} === true ? true : ${n} > ${t} ? ${n} : ${t}`))),mergeToName:(e,t,n)=>e.if(r._`${n} !== true`,(()=>e.assign(n,!0===t||r._`${n} > ${t} ? ${n} : ${t}`))),mergeValues:(e,t)=>!0===e||Math.max(e,t),resultToName:(e,t)=>e.var("items",t)})},t.evaluatedPropsToName=u,t.setEvaluated=p;const d={};var f;function h(e,t,n=e.opts.strictSchema){if(n){if(t=`strict mode: ${t}`,!0===n)throw new Error(t);e.self.logger.warn(t)}}t.useFunc=function(e,t){return e.scopeValue("func",{ref:t,code:d[t.code]||(d[t.code]=new o._Code(t.code))})},function(e){e[e.Num=0]="Num",e[e.Str=1]="Str"}(f=t.Type||(t.Type={})),t.getErrorPath=function(e,t,n){if(e instanceof r.Name){const o=t===f.Num;return n?o?r._`"[" + ${e} + "]"`:r._`"['" + ${e} + "']"`:o?r._`"/" + ${e}`:r._`"/" + ${e}.replace(/~/g, "~0").replace(/\\//g, "~1")`}return n?r.getProperty(e).toString():"/"+s(e)},t.checkStrictMode=h},4566:function(e,t){"use strict";function n(e,t){return t.rules.some((t=>r(e,t)))}function r(e,t){var n;return void 0!==e[t.keyword]||(null===(n=t.definition.implements)||void 0===n?void 0:n.some((t=>void 0!==e[t])))}Object.defineProperty(t,"__esModule",{value:!0}),t.shouldUseRule=t.shouldUseGroup=t.schemaHasRulesForType=void 0,t.schemaHasRulesForType=function({schema:e,self:t},r){const o=t.RULES.types[r];return o&&!0!==o&&n(e,o)},t.shouldUseGroup=n,t.shouldUseRule=r},7627:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.boolOrEmptySchema=t.topBoolOrEmptySchema=void 0;const r=n(1885),o=n(4475),i=n(5018),a={message:"boolean schema is false"};function s(e,t){const{gen:n,data:o}=e,i={gen:n,keyword:"false schema",data:o,schema:!1,schemaCode:!1,schemaValue:!1,params:{},it:e};r.reportError(i,a,void 0,t)}t.topBoolOrEmptySchema=function(e){const{gen:t,schema:n,validateName:r}=e;!1===n?s(e,!1):"object"==typeof n&&!0===n.$async?t.return(i.default.data):(t.assign(o._`${r}.errors`,null),t.return(!0))},t.boolOrEmptySchema=function(e,t){const{gen:n,schema:r}=e;!1===r?(n.var(t,!1),s(e)):n.var(t,!0)}},7927:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportTypeError=t.checkDataTypes=t.checkDataType=t.coerceAndCheckDataType=t.getJSONTypes=t.getSchemaTypes=t.DataType=void 0;const r=n(3664),o=n(4566),i=n(1885),a=n(4475),s=n(6124);var l;function c(e){const t=Array.isArray(e)?e:e?[e]:[];if(t.every(r.isJSONType))return t;throw new Error("type must be JSONType or JSONType[]: "+t.join(","))}!function(e){e[e.Correct=0]="Correct",e[e.Wrong=1]="Wrong"}(l=t.DataType||(t.DataType={})),t.getSchemaTypes=function(e){const t=c(e.type);if(t.includes("null")){if(!1===e.nullable)throw new Error("type: null contradicts nullable: false")}else{if(!t.length&&void 0!==e.nullable)throw new Error('"nullable" cannot be used without "type"');!0===e.nullable&&t.push("null")}return t},t.getJSONTypes=c,t.coerceAndCheckDataType=function(e,t){const{gen:n,data:r,opts:i}=e,s=function(e,t){return t?e.filter((e=>u.has(e)||"array"===t&&"array"===e)):[]}(t,i.coerceTypes),c=t.length>0&&!(0===s.length&&1===t.length&&o.schemaHasRulesForType(e,t[0]));if(c){const o=d(t,r,i.strictNumbers,l.Wrong);n.if(o,(()=>{s.length?function(e,t,n){const{gen:r,data:o,opts:i}=e,s=r.let("dataType",a._`typeof ${o}`),l=r.let("coerced",a._`undefined`);"array"===i.coerceTypes&&r.if(a._`${s} == 'object' && Array.isArray(${o}) && ${o}.length == 1`,(()=>r.assign(o,a._`${o}[0]`).assign(s,a._`typeof ${o}`).if(d(t,o,i.strictNumbers),(()=>r.assign(l,o))))),r.if(a._`${l} !== undefined`);for(const e of n)(u.has(e)||"array"===e&&"array"===i.coerceTypes)&&c(e);function c(e){switch(e){case"string":return void r.elseIf(a._`${s} == "number" || ${s} == "boolean"`).assign(l,a._`"" + ${o}`).elseIf(a._`${o} === null`).assign(l,a._`""`);case"number":return void r.elseIf(a._`${s} == "boolean" || ${o} === null + || (${s} == "string" && ${o} && ${o} == +${o})`).assign(l,a._`+${o}`);case"integer":return void r.elseIf(a._`${s} === "boolean" || ${o} === null + || (${s} === "string" && ${o} && ${o} == +${o} && !(${o} % 1))`).assign(l,a._`+${o}`);case"boolean":return void r.elseIf(a._`${o} === "false" || ${o} === 0 || ${o} === null`).assign(l,!1).elseIf(a._`${o} === "true" || ${o} === 1`).assign(l,!0);case"null":return r.elseIf(a._`${o} === "" || ${o} === 0 || ${o} === false`),void r.assign(l,null);case"array":r.elseIf(a._`${s} === "string" || ${s} === "number" + || ${s} === "boolean" || ${o} === null`).assign(l,a._`[${o}]`)}}r.else(),h(e),r.endIf(),r.if(a._`${l} !== undefined`,(()=>{r.assign(o,l),function({gen:e,parentData:t,parentDataProperty:n},r){e.if(a._`${t} !== undefined`,(()=>e.assign(a._`${t}[${n}]`,r)))}(e,l)}))}(e,t,s):h(e)}))}return c};const u=new Set(["string","number","integer","boolean","null"]);function p(e,t,n,r=l.Correct){const o=r===l.Correct?a.operators.EQ:a.operators.NEQ;let i;switch(e){case"null":return a._`${t} ${o} null`;case"array":i=a._`Array.isArray(${t})`;break;case"object":i=a._`${t} && typeof ${t} == "object" && !Array.isArray(${t})`;break;case"integer":i=s(a._`!(${t} % 1) && !isNaN(${t})`);break;case"number":i=s();break;default:return a._`typeof ${t} ${o} ${e}`}return r===l.Correct?i:a.not(i);function s(e=a.nil){return a.and(a._`typeof ${t} == "number"`,e,n?a._`isFinite(${t})`:a.nil)}}function d(e,t,n,r){if(1===e.length)return p(e[0],t,n,r);let o;const i=s.toHash(e);if(i.array&&i.object){const e=a._`typeof ${t} != "object"`;o=i.null?e:a._`!${t} || ${e}`,delete i.null,delete i.array,delete i.object}else o=a.nil;i.number&&delete i.integer;for(const e in i)o=a.and(o,p(e,t,n,r));return o}t.checkDataType=p,t.checkDataTypes=d;const f={message:({schema:e})=>`must be ${e}`,params:({schema:e,schemaValue:t})=>"string"==typeof e?a._`{type: ${e}}`:a._`{type: ${t}}`};function h(e){const t=function(e){const{gen:t,data:n,schema:r}=e,o=s.schemaRefOrVal(e,r,"type");return{gen:t,keyword:"type",data:n,schema:r.type,schemaCode:o,schemaValue:o,parentSchema:r,params:{},it:e}}(e);i.reportError(t,f)}t.reportTypeError=h},2537:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.assignDefaults=void 0;const r=n(4475),o=n(6124);function i(e,t,n){const{gen:i,compositeRule:a,data:s,opts:l}=e;if(void 0===n)return;const c=r._`${s}${r.getProperty(t)}`;if(a)return void o.checkStrictMode(e,`default is ignored for: ${c}`);let u=r._`${c} === undefined`;"empty"===l.useDefaults&&(u=r._`${u} || ${c} === null || ${c} === ""`),i.if(u,r._`${c} = ${r.stringify(n)}`)}t.assignDefaults=function(e,t){const{properties:n,items:r}=e.schema;if("object"===t&&n)for(const t in n)i(e,t,n[t].default);else"array"===t&&Array.isArray(r)&&r.forEach(((t,n)=>i(e,n,t.default)))}},1321:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getData=t.KeywordCxt=t.validateFunctionCode=void 0;const r=n(7627),o=n(7927),i=n(4566),a=n(7927),s=n(2537),l=n(6488),c=n(4688),u=n(4475),p=n(5018),d=n(9826),f=n(6124),h=n(1885);function m({gen:e,validateName:t,schema:n,schemaEnv:r,opts:o},i){o.code.es5?e.func(t,u._`${p.default.data}, ${p.default.valCxt}`,r.$async,(()=>{e.code(u._`"use strict"; ${g(n,o)}`),function(e,t){e.if(p.default.valCxt,(()=>{e.var(p.default.instancePath,u._`${p.default.valCxt}.${p.default.instancePath}`),e.var(p.default.parentData,u._`${p.default.valCxt}.${p.default.parentData}`),e.var(p.default.parentDataProperty,u._`${p.default.valCxt}.${p.default.parentDataProperty}`),e.var(p.default.rootData,u._`${p.default.valCxt}.${p.default.rootData}`),t.dynamicRef&&e.var(p.default.dynamicAnchors,u._`${p.default.valCxt}.${p.default.dynamicAnchors}`)}),(()=>{e.var(p.default.instancePath,u._`""`),e.var(p.default.parentData,u._`undefined`),e.var(p.default.parentDataProperty,u._`undefined`),e.var(p.default.rootData,p.default.data),t.dynamicRef&&e.var(p.default.dynamicAnchors,u._`{}`)}))}(e,o),e.code(i)})):e.func(t,u._`${p.default.data}, ${function(e){return u._`{${p.default.instancePath}="", ${p.default.parentData}, ${p.default.parentDataProperty}, ${p.default.rootData}=${p.default.data}${e.dynamicRef?u._`, ${p.default.dynamicAnchors}={}`:u.nil}}={}`}(o)}`,r.$async,(()=>e.code(g(n,o)).code(i)))}function g(e,t){const n="object"==typeof e&&e[t.schemaId];return n&&(t.code.source||t.code.process)?u._`/*# sourceURL=${n} */`:u.nil}function y({schema:e,self:t}){if("boolean"==typeof e)return!e;for(const n in e)if(t.RULES.all[n])return!0;return!1}function v(e){return"boolean"!=typeof e.schema}function b(e){f.checkUnknownRules(e),function(e){const{schema:t,errSchemaPath:n,opts:r,self:o}=e;t.$ref&&r.ignoreKeywordsWithRef&&f.schemaHasRulesButRef(t,o.RULES)&&o.logger.warn(`$ref: keywords ignored in schema at path "${n}"`)}(e)}function w(e,t){if(e.opts.jtd)return k(e,[],!1,t);const n=o.getSchemaTypes(e.schema);k(e,n,!o.coerceAndCheckDataType(e,n),t)}function x({gen:e,schemaEnv:t,schema:n,errSchemaPath:r,opts:o}){const i=n.$comment;if(!0===o.$comment)e.code(u._`${p.default.self}.logger.log(${i})`);else if("function"==typeof o.$comment){const n=u.str`${r}/$comment`,o=e.scopeValue("root",{ref:t.root});e.code(u._`${p.default.self}.opts.$comment(${i}, ${n}, ${o}.schema)`)}}function k(e,t,n,r){const{gen:o,schema:s,data:l,allErrors:c,opts:d,self:h}=e,{RULES:m}=h;function g(f){i.shouldUseGroup(s,f)&&(f.type?(o.if(a.checkDataType(f.type,l,d.strictNumbers)),_(e,f),1===t.length&&t[0]===f.type&&n&&(o.else(),a.reportTypeError(e)),o.endIf()):_(e,f),c||o.if(u._`${p.default.errors} === ${r||0}`))}!s.$ref||!d.ignoreKeywordsWithRef&&f.schemaHasRulesButRef(s,m)?(d.jtd||function(e,t){!e.schemaEnv.meta&&e.opts.strictTypes&&(function(e,t){t.length&&(e.dataTypes.length?(t.forEach((t=>{O(e.dataTypes,t)||S(e,`type "${t}" not allowed by context "${e.dataTypes.join(",")}"`)})),e.dataTypes=e.dataTypes.filter((e=>O(t,e)))):e.dataTypes=t)}(e,t),e.opts.allowUnionTypes||function(e,t){t.length>1&&(2!==t.length||!t.includes("null"))&&S(e,"use allowUnionTypes to allow union type keyword")}(e,t),function(e,t){const n=e.self.RULES.all;for(const r in n){const o=n[r];if("object"==typeof o&&i.shouldUseRule(e.schema,o)){const{type:n}=o.definition;n.length&&!n.some((e=>{return r=e,(n=t).includes(r)||"number"===r&&n.includes("integer");var n,r}))&&S(e,`missing type "${n.join(",")}" for keyword "${r}"`)}}}(e,e.dataTypes))}(e,t),o.block((()=>{for(const e of m.rules)g(e);g(m.post)}))):o.block((()=>P(e,"$ref",m.all.$ref.definition)))}function _(e,t){const{gen:n,schema:r,opts:{useDefaults:o}}=e;o&&s.assignDefaults(e,t.type),n.block((()=>{for(const n of t.rules)i.shouldUseRule(r,n)&&P(e,n.keyword,n.definition,t.type)}))}function O(e,t){return e.includes(t)||"integer"===t&&e.includes("number")}function S(e,t){t+=` at "${e.schemaEnv.baseId+e.errSchemaPath}" (strictTypes)`,f.checkStrictMode(e,t,e.opts.strictTypes)}t.validateFunctionCode=function(e){v(e)&&(b(e),y(e))?function(e){const{schema:t,opts:n,gen:r}=e;m(e,(()=>{n.$comment&&t.$comment&&x(e),function(e){const{schema:t,opts:n}=e;void 0!==t.default&&n.useDefaults&&n.strictSchema&&f.checkStrictMode(e,"default is ignored in the schema root")}(e),r.let(p.default.vErrors,null),r.let(p.default.errors,0),n.unevaluated&&function(e){const{gen:t,validateName:n}=e;e.evaluated=t.const("evaluated",u._`${n}.evaluated`),t.if(u._`${e.evaluated}.dynamicProps`,(()=>t.assign(u._`${e.evaluated}.props`,u._`undefined`))),t.if(u._`${e.evaluated}.dynamicItems`,(()=>t.assign(u._`${e.evaluated}.items`,u._`undefined`)))}(e),w(e),function(e){const{gen:t,schemaEnv:n,validateName:r,ValidationError:o,opts:i}=e;n.$async?t.if(u._`${p.default.errors} === 0`,(()=>t.return(p.default.data)),(()=>t.throw(u._`new ${o}(${p.default.vErrors})`))):(t.assign(u._`${r}.errors`,p.default.vErrors),i.unevaluated&&function({gen:e,evaluated:t,props:n,items:r}){n instanceof u.Name&&e.assign(u._`${t}.props`,n),r instanceof u.Name&&e.assign(u._`${t}.items`,r)}(e),t.return(u._`${p.default.errors} === 0`))}(e)}))}(e):m(e,(()=>r.topBoolOrEmptySchema(e)))};class E{constructor(e,t,n){if(l.validateKeywordUsage(e,t,n),this.gen=e.gen,this.allErrors=e.allErrors,this.keyword=n,this.data=e.data,this.schema=e.schema[n],this.$data=t.$data&&e.opts.$data&&this.schema&&this.schema.$data,this.schemaValue=f.schemaRefOrVal(e,this.schema,n,this.$data),this.schemaType=t.schemaType,this.parentSchema=e.schema,this.params={},this.it=e,this.def=t,this.$data)this.schemaCode=e.gen.const("vSchema",C(this.$data,e));else if(this.schemaCode=this.schemaValue,!l.validSchemaType(this.schema,t.schemaType,t.allowUndefined))throw new Error(`${n} value must be ${JSON.stringify(t.schemaType)}`);("code"in t?t.trackErrors:!1!==t.errors)&&(this.errsCount=e.gen.const("_errs",p.default.errors))}result(e,t,n){this.gen.if(u.not(e)),n?n():this.error(),t?(this.gen.else(),t(),this.allErrors&&this.gen.endIf()):this.allErrors?this.gen.endIf():this.gen.else()}pass(e,t){this.result(e,void 0,t)}fail(e){if(void 0===e)return this.error(),void(this.allErrors||this.gen.if(!1));this.gen.if(e),this.error(),this.allErrors?this.gen.endIf():this.gen.else()}fail$data(e){if(!this.$data)return this.fail(e);const{schemaCode:t}=this;this.fail(u._`${t} !== undefined && (${u.or(this.invalid$data(),e)})`)}error(e,t,n){if(t)return this.setParams(t),this._error(e,n),void this.setParams({});this._error(e,n)}_error(e,t){(e?h.reportExtraError:h.reportError)(this,this.def.error,t)}$dataError(){h.reportError(this,this.def.$dataError||h.keyword$DataError)}reset(){if(void 0===this.errsCount)throw new Error('add "trackErrors" to keyword definition');h.resetErrorsCount(this.gen,this.errsCount)}ok(e){this.allErrors||this.gen.if(e)}setParams(e,t){t?Object.assign(this.params,e):this.params=e}block$data(e,t,n=u.nil){this.gen.block((()=>{this.check$data(e,n),t()}))}check$data(e=u.nil,t=u.nil){if(!this.$data)return;const{gen:n,schemaCode:r,schemaType:o,def:i}=this;n.if(u.or(u._`${r} === undefined`,t)),e!==u.nil&&n.assign(e,!0),(o.length||i.validateSchema)&&(n.elseIf(this.invalid$data()),this.$dataError(),e!==u.nil&&n.assign(e,!1)),n.else()}invalid$data(){const{gen:e,schemaCode:t,schemaType:n,def:r,it:o}=this;return u.or(function(){if(n.length){if(!(t instanceof u.Name))throw new Error("ajv implementation error");const e=Array.isArray(n)?n:[n];return u._`${a.checkDataTypes(e,t,o.opts.strictNumbers,a.DataType.Wrong)}`}return u.nil}(),function(){if(r.validateSchema){const n=e.scopeValue("validate$data",{ref:r.validateSchema});return u._`!${n}(${t})`}return u.nil}())}subschema(e,t){const n=c.getSubschema(this.it,e);c.extendSubschemaData(n,this.it,e),c.extendSubschemaMode(n,e);const o={...this.it,...n,items:void 0,props:void 0};return function(e,t){v(e)&&(b(e),y(e))?function(e,t){const{schema:n,gen:r,opts:o}=e;o.$comment&&n.$comment&&x(e),function(e){const t=e.schema[e.opts.schemaId];t&&(e.baseId=d.resolveUrl(e.baseId,t))}(e),function(e){if(e.schema.$async&&!e.schemaEnv.$async)throw new Error("async schema in sync schema")}(e);const i=r.const("_errs",p.default.errors);w(e,i),r.var(t,u._`${i} === ${p.default.errors}`)}(e,t):r.boolOrEmptySchema(e,t)}(o,t),o}mergeEvaluated(e,t){const{it:n,gen:r}=this;n.opts.unevaluated&&(!0!==n.props&&void 0!==e.props&&(n.props=f.mergeEvaluated.props(r,e.props,n.props,t)),!0!==n.items&&void 0!==e.items&&(n.items=f.mergeEvaluated.items(r,e.items,n.items,t)))}mergeValidEvaluated(e,t){const{it:n,gen:r}=this;if(n.opts.unevaluated&&(!0!==n.props||!0!==n.items))return r.if(t,(()=>this.mergeEvaluated(e,u.Name))),!0}}function P(e,t,n,r){const o=new E(e,n,t);"code"in n?n.code(o,r):o.$data&&n.validate?l.funcKeywordCode(o,n):"macro"in n?l.macroKeywordCode(o,n):(n.compile||n.validate)&&l.funcKeywordCode(o,n)}t.KeywordCxt=E;const A=/^\/(?:[^~]|~0|~1)*$/,$=/^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;function C(e,{dataLevel:t,dataNames:n,dataPathArr:r}){let o,i;if(""===e)return p.default.rootData;if("/"===e[0]){if(!A.test(e))throw new Error(`Invalid JSON-pointer: ${e}`);o=e,i=p.default.rootData}else{const a=$.exec(e);if(!a)throw new Error(`Invalid JSON-pointer: ${e}`);const s=+a[1];if(o=a[2],"#"===o){if(s>=t)throw new Error(l("property/index",s));return r[t-s]}if(s>t)throw new Error(l("data",s));if(i=n[t-s],!o)return i}let a=i;const s=o.split("/");for(const e of s)e&&(i=u._`${i}${u.getProperty(f.unescapeJsonPointer(e))}`,a=u._`${a} && ${i}`);return a;function l(e,n){return`Cannot access ${e} ${n} levels up, current level is ${t}`}}t.getData=C},6488:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateKeywordUsage=t.validSchemaType=t.funcKeywordCode=t.macroKeywordCode=void 0;const r=n(4475),o=n(5018),i=n(8619),a=n(1885);function s(e){const{gen:t,data:n,it:o}=e;t.if(o.parentData,(()=>t.assign(n,r._`${o.parentData}[${o.parentDataProperty}]`)))}function l(e,t,n){if(void 0===n)throw new Error(`keyword "${t}" failed to compile`);return e.scopeValue("keyword","function"==typeof n?{ref:n}:{ref:n,code:r.stringify(n)})}t.macroKeywordCode=function(e,t){const{gen:n,keyword:o,schema:i,parentSchema:a,it:s}=e,c=t.macro.call(s.self,i,a,s),u=l(n,o,c);!1!==s.opts.validateSchema&&s.self.validateSchema(c,!0);const p=n.name("valid");e.subschema({schema:c,schemaPath:r.nil,errSchemaPath:`${s.errSchemaPath}/${o}`,topSchemaRef:u,compositeRule:!0},p),e.pass(p,(()=>e.error(!0)))},t.funcKeywordCode=function(e,t){var n;const{gen:c,keyword:u,schema:p,parentSchema:d,$data:f,it:h}=e;!function({schemaEnv:e},t){if(t.async&&!e.$async)throw new Error("async keyword in sync schema")}(h,t);const m=!f&&t.compile?t.compile.call(h.self,p,d,h):t.validate,g=l(c,u,m),y=c.let("valid");function v(n=(t.async?r._`await `:r.nil)){const a=h.opts.passContext?o.default.this:o.default.self,s=!("compile"in t&&!f||!1===t.schema);c.assign(y,r._`${n}${i.callValidateCode(e,g,a,s)}`,t.modifying)}function b(e){var n;c.if(r.not(null!==(n=t.valid)&&void 0!==n?n:y),e)}e.block$data(y,(function(){if(!1===t.errors)v(),t.modifying&&s(e),b((()=>e.error()));else{const n=t.async?function(){const e=c.let("ruleErrs",null);return c.try((()=>v(r._`await `)),(t=>c.assign(y,!1).if(r._`${t} instanceof ${h.ValidationError}`,(()=>c.assign(e,r._`${t}.errors`)),(()=>c.throw(t))))),e}():function(){const e=r._`${g}.errors`;return c.assign(e,null),v(r.nil),e}();t.modifying&&s(e),b((()=>function(e,t){const{gen:n}=e;n.if(r._`Array.isArray(${t})`,(()=>{n.assign(o.default.vErrors,r._`${o.default.vErrors} === null ? ${t} : ${o.default.vErrors}.concat(${t})`).assign(o.default.errors,r._`${o.default.vErrors}.length`),a.extendErrors(e)}),(()=>e.error()))}(e,n)))}})),e.ok(null!==(n=t.valid)&&void 0!==n?n:y)},t.validSchemaType=function(e,t,n=!1){return!t.length||t.some((t=>"array"===t?Array.isArray(e):"object"===t?e&&"object"==typeof e&&!Array.isArray(e):typeof e==t||n&&void 0===e))},t.validateKeywordUsage=function({schema:e,opts:t,self:n,errSchemaPath:r},o,i){if(Array.isArray(o.keyword)?!o.keyword.includes(i):o.keyword!==i)throw new Error("ajv implementation error");const a=o.dependencies;if(null==a?void 0:a.some((t=>!Object.prototype.hasOwnProperty.call(e,t))))throw new Error(`parent schema must have dependencies of ${i}: ${a.join(",")}`);if(o.validateSchema&&!o.validateSchema(e[i])){const e=`keyword "${i}" value is invalid at path "${r}": `+n.errorsText(o.validateSchema.errors);if("log"!==t.validateSchema)throw new Error(e);n.logger.error(e)}}},4688:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.extendSubschemaMode=t.extendSubschemaData=t.getSubschema=void 0;const r=n(4475),o=n(6124);t.getSubschema=function(e,{keyword:t,schemaProp:n,schema:i,schemaPath:a,errSchemaPath:s,topSchemaRef:l}){if(void 0!==t&&void 0!==i)throw new Error('both "keyword" and "schema" passed, only one allowed');if(void 0!==t){const i=e.schema[t];return void 0===n?{schema:i,schemaPath:r._`${e.schemaPath}${r.getProperty(t)}`,errSchemaPath:`${e.errSchemaPath}/${t}`}:{schema:i[n],schemaPath:r._`${e.schemaPath}${r.getProperty(t)}${r.getProperty(n)}`,errSchemaPath:`${e.errSchemaPath}/${t}/${o.escapeFragment(n)}`}}if(void 0!==i){if(void 0===a||void 0===s||void 0===l)throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"');return{schema:i,schemaPath:a,topSchemaRef:l,errSchemaPath:s}}throw new Error('either "keyword" or "schema" must be passed')},t.extendSubschemaData=function(e,t,{dataProp:n,dataPropType:i,data:a,dataTypes:s,propertyName:l}){if(void 0!==a&&void 0!==n)throw new Error('both "data" and "dataProp" passed, only one allowed');const{gen:c}=t;if(void 0!==n){const{errorPath:a,dataPathArr:s,opts:l}=t;u(c.let("data",r._`${t.data}${r.getProperty(n)}`,!0)),e.errorPath=r.str`${a}${o.getErrorPath(n,i,l.jsPropertySyntax)}`,e.parentDataProperty=r._`${n}`,e.dataPathArr=[...s,e.parentDataProperty]}function u(n){e.data=n,e.dataLevel=t.dataLevel+1,e.dataTypes=[],t.definedProperties=new Set,e.parentData=t.data,e.dataNames=[...t.dataNames,n]}void 0!==a&&(u(a instanceof r.Name?a:c.let("data",a,!0)),void 0!==l&&(e.propertyName=l)),s&&(e.dataTypes=s)},t.extendSubschemaMode=function(e,{jtdDiscriminator:t,jtdMetadata:n,compositeRule:r,createErrors:o,allErrors:i}){void 0!==r&&(e.compositeRule=r),void 0!==o&&(e.createErrors=o),void 0!==i&&(e.allErrors=i),e.jtdDiscriminator=t,e.jtdMetadata=n}},3325:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CodeGen=t.Name=t.nil=t.stringify=t.str=t._=t.KeywordCxt=void 0;var r=n(1321);Object.defineProperty(t,"KeywordCxt",{enumerable:!0,get:function(){return r.KeywordCxt}});var o=n(4475);Object.defineProperty(t,"_",{enumerable:!0,get:function(){return o._}}),Object.defineProperty(t,"str",{enumerable:!0,get:function(){return o.str}}),Object.defineProperty(t,"stringify",{enumerable:!0,get:function(){return o.stringify}}),Object.defineProperty(t,"nil",{enumerable:!0,get:function(){return o.nil}}),Object.defineProperty(t,"Name",{enumerable:!0,get:function(){return o.Name}}),Object.defineProperty(t,"CodeGen",{enumerable:!0,get:function(){return o.CodeGen}});const i=n(8451),a=n(4143),s=n(3664),l=n(7805),c=n(4475),u=n(9826),p=n(7927),d=n(6124),f=n(425),h=["removeAdditional","useDefaults","coerceTypes"],m=new Set(["validate","serialize","parse","wrapper","root","schema","keyword","pattern","formats","validate$data","func","obj","Error"]),g={errorDataPath:"",format:"`validateFormats: false` can be used instead.",nullable:'"nullable" keyword is supported by default.',jsonPointers:"Deprecated jsPropertySyntax can be used instead.",extendRefs:"Deprecated ignoreKeywordsWithRef can be used instead.",missingRefs:"Pass empty schema with $id that should be ignored to ajv.addSchema.",processCode:"Use option `code: {process: (code, schemaEnv: object) => string}`",sourceCode:"Use option `code: {source: true}`",strictDefaults:"It is default now, see option `strict`.",strictKeywords:"It is default now, see option `strict`.",uniqueItems:'"uniqueItems" keyword is always validated.',unknownFormats:"Disable strict mode or pass `true` to `ajv.addFormat` (or `formats` option).",cache:"Map is used as cache, schema object as key.",serialize:"Map is used as cache, schema object as key.",ajvErrors:"It is default now."},y={ignoreKeywordsWithRef:"",jsPropertySyntax:"",unicode:'"minLength"/"maxLength" account for unicode characters by default.'};function v(e){var t,n,r,o,i,a,s,l,c,u,p,d,f,h,m,g,y,v,b,w,x,k;const _=e.strict,O=null===(t=e.code)||void 0===t?void 0:t.optimize,S=!0===O||void 0===O?1:O||0;return{strictSchema:null===(r=null!==(n=e.strictSchema)&&void 0!==n?n:_)||void 0===r||r,strictNumbers:null===(i=null!==(o=e.strictNumbers)&&void 0!==o?o:_)||void 0===i||i,strictTypes:null!==(s=null!==(a=e.strictTypes)&&void 0!==a?a:_)&&void 0!==s?s:"log",strictTuples:null!==(c=null!==(l=e.strictTuples)&&void 0!==l?l:_)&&void 0!==c?c:"log",strictRequired:null!==(p=null!==(u=e.strictRequired)&&void 0!==u?u:_)&&void 0!==p&&p,code:e.code?{...e.code,optimize:S}:{optimize:S},loopRequired:null!==(d=e.loopRequired)&&void 0!==d?d:200,loopEnum:null!==(f=e.loopEnum)&&void 0!==f?f:200,meta:null===(h=e.meta)||void 0===h||h,messages:null===(m=e.messages)||void 0===m||m,inlineRefs:null===(g=e.inlineRefs)||void 0===g||g,schemaId:null!==(y=e.schemaId)&&void 0!==y?y:"$id",addUsedSchema:null===(v=e.addUsedSchema)||void 0===v||v,validateSchema:null===(b=e.validateSchema)||void 0===b||b,validateFormats:null===(w=e.validateFormats)||void 0===w||w,unicodeRegExp:null===(x=e.unicodeRegExp)||void 0===x||x,int32range:null===(k=e.int32range)||void 0===k||k}}class b{constructor(e={}){this.schemas={},this.refs={},this.formats={},this._compilations=new Set,this._loading={},this._cache=new Map,e=this.opts={...e,...v(e)};const{es5:t,lines:n}=this.opts.code;this.scope=new c.ValueScope({scope:{},prefixes:m,es5:t,lines:n}),this.logger=function(e){if(!1===e)return E;if(void 0===e)return console;if(e.log&&e.warn&&e.error)return e;throw new Error("logger must implement log, warn and error methods")}(e.logger);const r=e.validateFormats;e.validateFormats=!1,this.RULES=s.getRules(),w.call(this,g,e,"NOT SUPPORTED"),w.call(this,y,e,"DEPRECATED","warn"),this._metaOpts=S.call(this),e.formats&&_.call(this),this._addVocabularies(),this._addDefaultMetaSchema(),e.keywords&&O.call(this,e.keywords),"object"==typeof e.meta&&this.addMetaSchema(e.meta),k.call(this),e.validateFormats=r}_addVocabularies(){this.addKeyword("$async")}_addDefaultMetaSchema(){const{$data:e,meta:t,schemaId:n}=this.opts;let r=f;"id"===n&&(r={...f},r.id=r.$id,delete r.$id),t&&e&&this.addMetaSchema(r,r[n],!1)}defaultMeta(){const{meta:e,schemaId:t}=this.opts;return this.opts.defaultMeta="object"==typeof e?e[t]||e:void 0}validate(e,t){let n;if("string"==typeof e){if(n=this.getSchema(e),!n)throw new Error(`no schema with key or ref "${e}"`)}else n=this.compile(e);const r=n(t);return"$async"in n||(this.errors=n.errors),r}compile(e,t){const n=this._addSchema(e,t);return n.validate||this._compileSchemaEnv(n)}compileAsync(e,t){if("function"!=typeof this.opts.loadSchema)throw new Error("options.loadSchema should be a function");const{loadSchema:n}=this.opts;return r.call(this,e,t);async function r(e,t){await o.call(this,e.$schema);const n=this._addSchema(e,t);return n.validate||i.call(this,n)}async function o(e){e&&!this.getSchema(e)&&await r.call(this,{$ref:e},!0)}async function i(e){try{return this._compileSchemaEnv(e)}catch(t){if(!(t instanceof a.default))throw t;return s.call(this,t),await l.call(this,t.missingSchema),i.call(this,e)}}function s({missingSchema:e,missingRef:t}){if(this.refs[e])throw new Error(`AnySchema ${e} is loaded but ${t} cannot be resolved`)}async function l(e){const n=await c.call(this,e);this.refs[e]||await o.call(this,n.$schema),this.refs[e]||this.addSchema(n,e,t)}async function c(e){const t=this._loading[e];if(t)return t;try{return await(this._loading[e]=n(e))}finally{delete this._loading[e]}}}addSchema(e,t,n,r=this.opts.validateSchema){if(Array.isArray(e)){for(const t of e)this.addSchema(t,void 0,n,r);return this}let o;if("object"==typeof e){const{schemaId:t}=this.opts;if(o=e[t],void 0!==o&&"string"!=typeof o)throw new Error(`schema ${t} must be string`)}return t=u.normalizeId(t||o),this._checkUnique(t),this.schemas[t]=this._addSchema(e,n,t,r,!0),this}addMetaSchema(e,t,n=this.opts.validateSchema){return this.addSchema(e,t,!0,n),this}validateSchema(e,t){if("boolean"==typeof e)return!0;let n;if(n=e.$schema,void 0!==n&&"string"!=typeof n)throw new Error("$schema must be a string");if(n=n||this.opts.defaultMeta||this.defaultMeta(),!n)return this.logger.warn("meta-schema not available"),this.errors=null,!0;const r=this.validate(n,e);if(!r&&t){const e="schema is invalid: "+this.errorsText();if("log"!==this.opts.validateSchema)throw new Error(e);this.logger.error(e)}return r}getSchema(e){let t;for(;"string"==typeof(t=x.call(this,e));)e=t;if(void 0===t){const{schemaId:n}=this.opts,r=new l.SchemaEnv({schema:{},schemaId:n});if(t=l.resolveSchema.call(this,r,e),!t)return;this.refs[e]=t}return t.validate||this._compileSchemaEnv(t)}removeSchema(e){if(e instanceof RegExp)return this._removeAllSchemas(this.schemas,e),this._removeAllSchemas(this.refs,e),this;switch(typeof e){case"undefined":return this._removeAllSchemas(this.schemas),this._removeAllSchemas(this.refs),this._cache.clear(),this;case"string":{const t=x.call(this,e);return"object"==typeof t&&this._cache.delete(t.schema),delete this.schemas[e],delete this.refs[e],this}case"object":{const t=e;this._cache.delete(t);let n=e[this.opts.schemaId];return n&&(n=u.normalizeId(n),delete this.schemas[n],delete this.refs[n]),this}default:throw new Error("ajv.removeSchema: invalid parameter")}}addVocabulary(e){for(const t of e)this.addKeyword(t);return this}addKeyword(e,t){let n;if("string"==typeof e)n=e,"object"==typeof t&&(this.logger.warn("these parameters are deprecated, see docs for addKeyword"),t.keyword=n);else{if("object"!=typeof e||void 0!==t)throw new Error("invalid addKeywords parameters");if(n=(t=e).keyword,Array.isArray(n)&&!n.length)throw new Error("addKeywords: keyword must be string or non-empty array")}if(A.call(this,n,t),!t)return d.eachItem(n,(e=>$.call(this,e))),this;R.call(this,t);const r={...t,type:p.getJSONTypes(t.type),schemaType:p.getJSONTypes(t.schemaType)};return d.eachItem(n,0===r.type.length?e=>$.call(this,e,r):e=>r.type.forEach((t=>$.call(this,e,r,t)))),this}getKeyword(e){const t=this.RULES.all[e];return"object"==typeof t?t.definition:!!t}removeKeyword(e){const{RULES:t}=this;delete t.keywords[e],delete t.all[e];for(const n of t.rules){const t=n.rules.findIndex((t=>t.keyword===e));t>=0&&n.rules.splice(t,1)}return this}addFormat(e,t){return"string"==typeof t&&(t=new RegExp(t)),this.formats[e]=t,this}errorsText(e=this.errors,{separator:t=", ",dataVar:n="data"}={}){return e&&0!==e.length?e.map((e=>`${n}${e.instancePath} ${e.message}`)).reduce(((e,n)=>e+t+n)):"No errors"}$dataMetaSchema(e,t){const n=this.RULES.all;e=JSON.parse(JSON.stringify(e));for(const r of t){const t=r.split("/").slice(1);let o=e;for(const e of t)o=o[e];for(const e in n){const t=n[e];if("object"!=typeof t)continue;const{$data:r}=t.definition,i=o[e];r&&i&&(o[e]=T(i))}}return e}_removeAllSchemas(e,t){for(const n in e){const r=e[n];t&&!t.test(n)||("string"==typeof r?delete e[n]:r&&!r.meta&&(this._cache.delete(r.schema),delete e[n]))}}_addSchema(e,t,n,r=this.opts.validateSchema,o=this.opts.addUsedSchema){let i;const{schemaId:a}=this.opts;if("object"==typeof e)i=e[a];else{if(this.opts.jtd)throw new Error("schema must be object");if("boolean"!=typeof e)throw new Error("schema must be object or boolean")}let s=this._cache.get(e);if(void 0!==s)return s;const c=u.getSchemaRefs.call(this,e);return n=u.normalizeId(i||n),s=new l.SchemaEnv({schema:e,schemaId:a,meta:t,baseId:n,localRefs:c}),this._cache.set(s.schema,s),o&&!n.startsWith("#")&&(n&&this._checkUnique(n),this.refs[n]=s),r&&this.validateSchema(e,!0),s}_checkUnique(e){if(this.schemas[e]||this.refs[e])throw new Error(`schema with key or id "${e}" already exists`)}_compileSchemaEnv(e){if(e.meta?this._compileMetaSchema(e):l.compileSchema.call(this,e),!e.validate)throw new Error("ajv implementation error");return e.validate}_compileMetaSchema(e){const t=this.opts;this.opts=this._metaOpts;try{l.compileSchema.call(this,e)}finally{this.opts=t}}}function w(e,t,n,r="error"){for(const o in e){const i=o;i in t&&this.logger[r](`${n}: option ${o}. ${e[i]}`)}}function x(e){return e=u.normalizeId(e),this.schemas[e]||this.refs[e]}function k(){const e=this.opts.schemas;if(e)if(Array.isArray(e))this.addSchema(e);else for(const t in e)this.addSchema(e[t],t)}function _(){for(const e in this.opts.formats){const t=this.opts.formats[e];t&&this.addFormat(e,t)}}function O(e){if(Array.isArray(e))this.addVocabulary(e);else{this.logger.warn("keywords option as map is deprecated, pass array");for(const t in e){const n=e[t];n.keyword||(n.keyword=t),this.addKeyword(n)}}}function S(){const e={...this.opts};for(const t of h)delete e[t];return e}t.default=b,b.ValidationError=i.default,b.MissingRefError=a.default;const E={log(){},warn(){},error(){}},P=/^[a-z_$][a-z0-9_$:-]*$/i;function A(e,t){const{RULES:n}=this;if(d.eachItem(e,(e=>{if(n.keywords[e])throw new Error(`Keyword ${e} is already defined`);if(!P.test(e))throw new Error(`Keyword ${e} has invalid name`)})),t&&t.$data&&!("code"in t)&&!("validate"in t))throw new Error('$data keyword must have "code" or "validate" function')}function $(e,t,n){var r;const o=null==t?void 0:t.post;if(n&&o)throw new Error('keyword with "post" flag cannot have "type"');const{RULES:i}=this;let a=o?i.post:i.rules.find((({type:e})=>e===n));if(a||(a={type:n,rules:[]},i.rules.push(a)),i.keywords[e]=!0,!t)return;const s={keyword:e,definition:{...t,type:p.getJSONTypes(t.type),schemaType:p.getJSONTypes(t.schemaType)}};t.before?C.call(this,a,s,t.before):a.rules.push(s),i.all[e]=s,null===(r=t.implements)||void 0===r||r.forEach((e=>this.addKeyword(e)))}function C(e,t,n){const r=e.rules.findIndex((e=>e.keyword===n));r>=0?e.rules.splice(r,0,t):(e.rules.push(t),this.logger.warn(`rule ${n} is not defined`))}function R(e){let{metaSchema:t}=e;void 0!==t&&(e.$data&&this.opts.$data&&(t=T(t)),e.validateSchema=this.compile(t,!0))}const j={$ref:"https://raw.githubusercontent.com/ajv-validator/ajv/master/lib/refs/data.json#"};function T(e){return{anyOf:[e,j]}}},412:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4063);r.code='require("ajv/dist/runtime/equal").default',t.default=r},5872:function(e,t){"use strict";function n(e){const t=e.length;let n,r=0,o=0;for(;o=55296&&n<=56319&&or.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>r._`{limit: ${e}}`},code(e){const{parentSchema:t,it:n}=e,{items:r}=t;Array.isArray(r)?a(e,r):o.checkStrictMode(n,'"additionalItems" is ignored when "items" is not an array of schemas')}};function a(e,t){const{gen:n,schema:i,data:a,keyword:s,it:l}=e;l.items=!0;const c=n.const("len",r._`${a}.length`);if(!1===i)e.setParams({len:t.length}),e.pass(r._`${c} <= ${t.length}`);else if("object"==typeof i&&!o.alwaysValidSchema(l,i)){const i=n.var("valid",r._`${c} <= ${t.length}`);n.if(r.not(i),(()=>function(i){n.forRange("i",t.length,c,(t=>{e.subschema({keyword:s,dataProp:t,dataPropType:o.Type.Num},i),l.allErrors||n.if(r.not(i),(()=>n.break()))}))}(i))),e.ok(i)}}t.validateAdditionalItems=a,t.default=i},1422:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(5018),a=n(6124),s={keyword:"additionalProperties",type:["object"],schemaType:["boolean","object"],allowUndefined:!0,trackErrors:!0,error:{message:"must NOT have additional properties",params:({params:e})=>o._`{additionalProperty: ${e.additionalProperty}}`},code(e){const{gen:t,parentSchema:n,data:s,errsCount:l,it:c}=e,{schema:u=c.opts.defaultAdditionalProperties}=e;if(!l)throw new Error("ajv implementation error");const{allErrors:p,opts:d}=c;if(c.props=!0,"all"!==d.removeAdditional&&a.alwaysValidSchema(c,u))return;const f=r.allSchemaProperties(n.properties),h=r.allSchemaProperties(n.patternProperties);function m(e){t.code(o._`delete ${s}[${e}]`)}function g(n){if("all"===d.removeAdditional||d.removeAdditional&&!1===u)m(n);else{if(!1===u)return e.setParams({additionalProperty:n}),e.error(),void(p||t.break());if("object"==typeof u&&!a.alwaysValidSchema(c,u)){const r=t.name("valid");"failing"===d.removeAdditional?(y(n,r,!1),t.if(o.not(r),(()=>{e.reset(),m(n)}))):(y(n,r),p||t.if(o.not(r),(()=>t.break())))}}}function y(t,n,r){const o={keyword:"additionalProperties",dataProp:t,dataPropType:a.Type.Str};!1===r&&Object.assign(o,{compositeRule:!0,createErrors:!1,allErrors:!1}),e.subschema(o,n)}t.forIn("key",s,(i=>{f.length||h.length?t.if(function(i){let s;if(f.length>8){const e=a.schemaRefOrVal(c,n.properties,"properties");s=r.isOwnProperty(t,e,i)}else s=f.length?o.or(...f.map((e=>o._`${i} === ${e}`))):o.nil;return h.length&&(s=o.or(s,...h.map((t=>o._`${r.usePattern(e,t)}.test(${i})`)))),o.not(s)}(i),(()=>g(i))):g(i)})),e.ok(o._`${l} === ${i.default.errors}`)}};t.default=s},5716:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:"allOf",schemaType:"array",code(e){const{gen:t,schema:n,it:o}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");const i=t.name("valid");n.forEach(((t,n)=>{if(r.alwaysValidSchema(o,t))return;const a=e.subschema({keyword:"allOf",schemaProp:n},i);e.ok(i),e.mergeEvaluated(a)}))}};t.default=o},1668:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r={keyword:"anyOf",schemaType:"array",trackErrors:!0,code:n(8619).validateUnion,error:{message:"must match a schema in anyOf"}};t.default=r},9564:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"contains",type:"array",schemaType:["object","boolean"],before:"uniqueItems",trackErrors:!0,error:{message:({params:{min:e,max:t}})=>void 0===t?r.str`must contain at least ${e} valid item(s)`:r.str`must contain at least ${e} and no more than ${t} valid item(s)`,params:({params:{min:e,max:t}})=>void 0===t?r._`{minContains: ${e}}`:r._`{minContains: ${e}, maxContains: ${t}}`},code(e){const{gen:t,schema:n,parentSchema:i,data:a,it:s}=e;let l,c;const{minContains:u,maxContains:p}=i;s.opts.next?(l=void 0===u?1:u,c=p):l=1;const d=t.const("len",r._`${a}.length`);if(e.setParams({min:l,max:c}),void 0===c&&0===l)return void o.checkStrictMode(s,'"minContains" == 0 without "maxContains": "contains" keyword ignored');if(void 0!==c&&l>c)return o.checkStrictMode(s,'"minContains" > "maxContains" is always invalid'),void e.fail();if(o.alwaysValidSchema(s,n)){let t=r._`${d} >= ${l}`;return void 0!==c&&(t=r._`${t} && ${d} <= ${c}`),void e.pass(t)}s.items=!0;const f=t.name("valid");if(void 0===c&&1===l)h(f,(()=>t.if(f,(()=>t.break()))));else{t.let(f,!1);const e=t.name("_valid"),n=t.let("count",0);h(e,(()=>t.if(e,(()=>function(e){t.code(r._`${e}++`),void 0===c?t.if(r._`${e} >= ${l}`,(()=>t.assign(f,!0).break())):(t.if(r._`${e} > ${c}`,(()=>t.assign(f,!1).break())),1===l?t.assign(f,!0):t.if(r._`${e} >= ${l}`,(()=>t.assign(f,!0))))}(n)))))}function h(n,r){t.forRange("i",0,d,(t=>{e.subschema({keyword:"contains",dataProp:t,dataPropType:o.Type.Num,compositeRule:!0},n),r()}))}e.result(f,(()=>e.reset()))}};t.default=i},1117:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateSchemaDeps=t.validatePropertyDeps=t.error=void 0;const r=n(4475),o=n(6124),i=n(8619);t.error={message:({params:{property:e,depsCount:t,deps:n}})=>{const o=1===t?"property":"properties";return r.str`must have ${o} ${n} when property ${e} is present`},params:({params:{property:e,depsCount:t,deps:n,missingProperty:o}})=>r._`{property: ${e}, + missingProperty: ${o}, + depsCount: ${t}, + deps: ${n}}`};const a={keyword:"dependencies",type:"object",schemaType:"object",error:t.error,code(e){const[t,n]=function({schema:e}){const t={},n={};for(const r in e)"__proto__"!==r&&((Array.isArray(e[r])?t:n)[r]=e[r]);return[t,n]}(e);s(e,t),l(e,n)}};function s(e,t=e.schema){const{gen:n,data:o,it:a}=e;if(0===Object.keys(t).length)return;const s=n.let("missing");for(const l in t){const c=t[l];if(0===c.length)continue;const u=i.propertyInData(n,o,l,a.opts.ownProperties);e.setParams({property:l,depsCount:c.length,deps:c.join(", ")}),a.allErrors?n.if(u,(()=>{for(const t of c)i.checkReportMissingProp(e,t)})):(n.if(r._`${u} && (${i.checkMissingProp(e,c,s)})`),i.reportMissingProp(e,s),n.else())}}function l(e,t=e.schema){const{gen:n,data:r,keyword:a,it:s}=e,l=n.name("valid");for(const c in t)o.alwaysValidSchema(s,t[c])||(n.if(i.propertyInData(n,r,c,s.opts.ownProperties),(()=>{const t=e.subschema({keyword:a,schemaProp:c},l);e.mergeValidEvaluated(t,l)}),(()=>n.var(l,!0))),e.ok(l))}t.validatePropertyDeps=s,t.validateSchemaDeps=l,t.default=a},5184:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"if",schemaType:["object","boolean"],trackErrors:!0,error:{message:({params:e})=>r.str`must match "${e.ifClause}" schema`,params:({params:e})=>r._`{failingKeyword: ${e.ifClause}}`},code(e){const{gen:t,parentSchema:n,it:i}=e;void 0===n.then&&void 0===n.else&&o.checkStrictMode(i,'"if" without "then" and "else" is ignored');const s=a(i,"then"),l=a(i,"else");if(!s&&!l)return;const c=t.let("valid",!0),u=t.name("_valid");if(function(){const t=e.subschema({keyword:"if",compositeRule:!0,createErrors:!1,allErrors:!1},u);e.mergeEvaluated(t)}(),e.reset(),s&&l){const n=t.let("ifClause");e.setParams({ifClause:n}),t.if(u,p("then",n),p("else",n))}else s?t.if(u,p("then")):t.if(r.not(u),p("else"));function p(n,o){return()=>{const i=e.subschema({keyword:n},u);t.assign(c,u),e.mergeValidEvaluated(i,c),o?t.assign(o,r._`${n}`):e.setParams({ifClause:n})}}e.pass(c,(()=>e.error(!0)))}};function a(e,t){const n=e.schema[t];return void 0!==n&&!o.alwaysValidSchema(e,n)}t.default=i},9616:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(3074),o=n(6988),i=n(6348),a=n(9822),s=n(9564),l=n(1117),c=n(4002),u=n(1422),p=n(9690),d=n(9883),f=n(8435),h=n(1668),m=n(9684),g=n(5716),y=n(5184),v=n(5642);t.default=function(e=!1){const t=[f.default,h.default,m.default,g.default,y.default,v.default,c.default,u.default,l.default,p.default,d.default];return e?t.push(o.default,a.default):t.push(r.default,i.default),t.push(s.default),t}},6348:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateTuple=void 0;const r=n(4475),o=n(6124),i=n(8619),a={keyword:"items",type:"array",schemaType:["object","array","boolean"],before:"uniqueItems",code(e){const{schema:t,it:n}=e;if(Array.isArray(t))return s(e,"additionalItems",t);n.items=!0,o.alwaysValidSchema(n,t)||e.ok(i.validateArray(e))}};function s(e,t,n=e.schema){const{gen:i,parentSchema:a,data:s,keyword:l,it:c}=e;!function(e){const{opts:r,errSchemaPath:i}=c,a=n.length,s=a===e.minItems&&(a===e.maxItems||!1===e[t]);if(r.strictTuples&&!s){const e=`"${l}" is ${a}-tuple, but minItems or maxItems/${t} are not specified or different at path "${i}"`;o.checkStrictMode(c,e,r.strictTuples)}}(a),c.opts.unevaluated&&n.length&&!0!==c.items&&(c.items=o.mergeEvaluated.items(i,n.length,c.items));const u=i.name("valid"),p=i.const("len",r._`${s}.length`);n.forEach(((t,n)=>{o.alwaysValidSchema(c,t)||(i.if(r._`${p} > ${n}`,(()=>e.subschema({keyword:l,schemaProp:n,dataProp:n},u))),e.ok(u))}))}t.validateTuple=s,t.default=a},9822:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(8619),a=n(3074),s={keyword:"items",type:"array",schemaType:["object","boolean"],before:"uniqueItems",error:{message:({params:{len:e}})=>r.str`must NOT have more than ${e} items`,params:({params:{len:e}})=>r._`{limit: ${e}}`},code(e){const{schema:t,parentSchema:n,it:r}=e,{prefixItems:s}=n;r.items=!0,o.alwaysValidSchema(r,t)||(s?a.validateAdditionalItems(e,s):e.ok(i.validateArray(e)))}};t.default=s},8435:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:"not",schemaType:["object","boolean"],trackErrors:!0,code(e){const{gen:t,schema:n,it:o}=e;if(r.alwaysValidSchema(o,n))return void e.fail();const i=t.name("valid");e.subschema({keyword:"not",compositeRule:!0,createErrors:!1,allErrors:!1},i),e.result(i,(()=>e.error()),(()=>e.reset()))},error:{message:"must NOT be valid"}};t.default=o},9684:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"oneOf",schemaType:"array",trackErrors:!0,error:{message:"must match exactly one schema in oneOf",params:({params:e})=>r._`{passingSchemas: ${e.passing}}`},code(e){const{gen:t,schema:n,parentSchema:i,it:a}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");if(a.opts.discriminator&&i.discriminator)return;const s=n,l=t.let("valid",!1),c=t.let("passing",null),u=t.name("_valid");e.setParams({passing:c}),t.block((function(){s.forEach(((n,i)=>{let s;o.alwaysValidSchema(a,n)?t.var(u,!0):s=e.subschema({keyword:"oneOf",schemaProp:i,compositeRule:!0},u),i>0&&t.if(r._`${u} && ${l}`).assign(l,!1).assign(c,r._`[${c}, ${i}]`).else(),t.if(u,(()=>{t.assign(l,!0),t.assign(c,i),s&&e.mergeEvaluated(s,r.Name)}))}))})),e.result(l,(()=>e.reset()),(()=>e.error(!0)))}};t.default=i},9883:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(6124),a=n(6124),s={keyword:"patternProperties",type:"object",schemaType:"object",code(e){const{gen:t,schema:n,data:s,parentSchema:l,it:c}=e,{opts:u}=c,p=r.allSchemaProperties(n),d=p.filter((e=>i.alwaysValidSchema(c,n[e])));if(0===p.length||d.length===p.length&&(!c.opts.unevaluated||!0===c.props))return;const f=u.strictSchema&&!u.allowMatchingProperties&&l.properties,h=t.name("valid");!0===c.props||c.props instanceof o.Name||(c.props=a.evaluatedPropsToName(t,c.props));const{props:m}=c;function g(e){for(const t in f)new RegExp(e).test(t)&&i.checkStrictMode(c,`property ${t} matches pattern ${e} (use allowMatchingProperties)`)}function y(n){t.forIn("key",s,(i=>{t.if(o._`${r.usePattern(e,n)}.test(${i})`,(()=>{const r=d.includes(n);r||e.subschema({keyword:"patternProperties",schemaProp:n,dataProp:i,dataPropType:a.Type.Str},h),c.opts.unevaluated&&!0!==m?t.assign(o._`${m}[${i}]`,!0):r||c.allErrors||t.if(o.not(h),(()=>t.break()))}))}))}!function(){for(const e of p)f&&g(e),c.allErrors?y(e):(t.var(h,!0),y(e),t.if(h))}()}};t.default=s},6988:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6348),o={keyword:"prefixItems",type:"array",schemaType:["array"],before:"uniqueItems",code:e=>r.validateTuple(e,"items")};t.default=o},9690:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(1321),o=n(8619),i=n(6124),a=n(1422),s={keyword:"properties",type:"object",schemaType:"object",code(e){const{gen:t,schema:n,parentSchema:s,data:l,it:c}=e;("all"===c.opts.removeAdditional&&void 0===s.additionalProperties||!1===c.opts.defaultAdditionalProperties)&&a.default.code(new r.KeywordCxt(c,a.default,"additionalProperties"));const u=o.allSchemaProperties(n);for(const e of u)c.definedProperties.add(e);c.opts.unevaluated&&u.length&&!0!==c.props&&(c.props=i.mergeEvaluated.props(t,i.toHash(u),c.props));const p=u.filter((e=>!i.alwaysValidSchema(c,n[e])));if(0===p.length)return;const d=t.name("valid");for(const n of p)f(n)?h(n):(t.if(o.propertyInData(t,l,n,c.opts.ownProperties)),h(n),c.allErrors||t.else().var(d,!0),t.endIf()),e.it.definedProperties.add(n),e.ok(d);function f(e){return c.opts.useDefaults&&!c.compositeRule&&void 0!==n[e].default}function h(t){e.subschema({keyword:"properties",schemaProp:t,dataProp:t},d)}}};t.default=s},4002:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i={keyword:"propertyNames",type:"object",schemaType:["object","boolean"],error:{message:"property name must be valid",params:({params:e})=>r._`{propertyName: ${e.propertyName}}`},code(e){const{gen:t,schema:n,data:i,it:a}=e;if(o.alwaysValidSchema(a,n))return;const s=t.name("valid");t.forIn("key",i,(n=>{e.setParams({propertyName:n}),e.subschema({keyword:"propertyNames",data:n,dataTypes:["string"],propertyName:n,compositeRule:!0},s),t.if(r.not(s),(()=>{e.error(!0),a.allErrors||t.break()}))})),e.ok(s)}};t.default=i},5642:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(6124),o={keyword:["then","else"],schemaType:["object","boolean"],code({keyword:e,parentSchema:t,it:n}){void 0===t.if&&r.checkStrictMode(n,`"${e}" without "if" is ignored`)}};t.default=o},8619:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateUnion=t.validateArray=t.usePattern=t.callValidateCode=t.schemaProperties=t.allSchemaProperties=t.noPropertyInData=t.propertyInData=t.isOwnProperty=t.hasPropFunc=t.reportMissingProp=t.checkMissingProp=t.checkReportMissingProp=void 0;const r=n(4475),o=n(6124),i=n(5018);function a(e){return e.scopeValue("func",{ref:Object.prototype.hasOwnProperty,code:r._`Object.prototype.hasOwnProperty`})}function s(e,t,n){return r._`${a(e)}.call(${t}, ${n})`}function l(e,t,n,o){const i=r._`${t}${r.getProperty(n)} === undefined`;return o?r.or(i,r.not(s(e,t,n))):i}function c(e){return e?Object.keys(e).filter((e=>"__proto__"!==e)):[]}t.checkReportMissingProp=function(e,t){const{gen:n,data:o,it:i}=e;n.if(l(n,o,t,i.opts.ownProperties),(()=>{e.setParams({missingProperty:r._`${t}`},!0),e.error()}))},t.checkMissingProp=function({gen:e,data:t,it:{opts:n}},o,i){return r.or(...o.map((o=>r.and(l(e,t,o,n.ownProperties),r._`${i} = ${o}`))))},t.reportMissingProp=function(e,t){e.setParams({missingProperty:t},!0),e.error()},t.hasPropFunc=a,t.isOwnProperty=s,t.propertyInData=function(e,t,n,o){const i=r._`${t}${r.getProperty(n)} !== undefined`;return o?r._`${i} && ${s(e,t,n)}`:i},t.noPropertyInData=l,t.allSchemaProperties=c,t.schemaProperties=function(e,t){return c(t).filter((n=>!o.alwaysValidSchema(e,t[n])))},t.callValidateCode=function({schemaCode:e,data:t,it:{gen:n,topSchemaRef:o,schemaPath:a,errorPath:s},it:l},c,u,p){const d=p?r._`${e}, ${t}, ${o}${a}`:t,f=[[i.default.instancePath,r.strConcat(i.default.instancePath,s)],[i.default.parentData,l.parentData],[i.default.parentDataProperty,l.parentDataProperty],[i.default.rootData,i.default.rootData]];l.opts.dynamicRef&&f.push([i.default.dynamicAnchors,i.default.dynamicAnchors]);const h=r._`${d}, ${n.object(...f)}`;return u!==r.nil?r._`${c}.call(${u}, ${h})`:r._`${c}(${h})`},t.usePattern=function({gen:e,it:{opts:t}},n){const o=t.unicodeRegExp?"u":"";return e.scopeValue("pattern",{key:n,ref:new RegExp(n,o),code:r._`new RegExp(${n}, ${o})`})},t.validateArray=function(e){const{gen:t,data:n,keyword:i,it:a}=e,s=t.name("valid");if(a.allErrors){const e=t.let("valid",!0);return l((()=>t.assign(e,!1))),e}return t.var(s,!0),l((()=>t.break())),s;function l(a){const l=t.const("len",r._`${n}.length`);t.forRange("i",0,l,(n=>{e.subschema({keyword:i,dataProp:n,dataPropType:o.Type.Num},s),t.if(r.not(s),a)}))}},t.validateUnion=function(e){const{gen:t,schema:n,keyword:i,it:a}=e;if(!Array.isArray(n))throw new Error("ajv implementation error");if(n.some((e=>o.alwaysValidSchema(a,e)))&&!a.opts.unevaluated)return;const s=t.let("valid",!1),l=t.name("_valid");t.block((()=>n.forEach(((n,o)=>{const a=e.subschema({keyword:i,schemaProp:o,compositeRule:!0},l);t.assign(s,r._`${s} || ${l}`),e.mergeValidEvaluated(a,l)||t.if(r.not(s))})))),e.result(s,(()=>e.reset()),(()=>e.error(!0)))}},5060:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n={keyword:"id",code(){throw new Error('NOT SUPPORTED: keyword "id", use "$id" for schema ID')}};t.default=n},8223:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(5060),o=n(4028),i=["$schema","$id","$defs","$vocabulary",{keyword:"$comment"},"definitions",r.default,o.default];t.default=i},4028:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.callRef=t.getValidate=void 0;const r=n(4143),o=n(8619),i=n(4475),a=n(5018),s=n(7805),l=n(6124),c={keyword:"$ref",schemaType:"string",code(e){const{gen:t,schema:n,it:o}=e,{baseId:a,schemaEnv:l,validateName:c,opts:d,self:f}=o,{root:h}=l;if(("#"===n||"#/"===n)&&a===h.baseId)return function(){if(l===h)return p(e,c,l,l.$async);const n=t.scopeValue("root",{ref:h});return p(e,i._`${n}.validate`,h,h.$async)}();const m=s.resolveRef.call(f,h,a,n);if(void 0===m)throw new r.default(a,n);return m instanceof s.SchemaEnv?function(t){const n=u(e,t);p(e,n,t,t.$async)}(m):function(r){const o=t.scopeValue("schema",!0===d.code.source?{ref:r,code:i.stringify(r)}:{ref:r}),a=t.name("valid"),s=e.subschema({schema:r,dataTypes:[],schemaPath:i.nil,topSchemaRef:o,errSchemaPath:n},a);e.mergeEvaluated(s),e.ok(a)}(m)}};function u(e,t){const{gen:n}=e;return t.validate?n.scopeValue("validate",{ref:t.validate}):i._`${n.scopeValue("wrapper",{ref:t})}.validate`}function p(e,t,n,r){const{gen:s,it:c}=e,{allErrors:u,schemaEnv:p,opts:d}=c,f=d.passContext?a.default.this:i.nil;function h(e){const t=i._`${e}.errors`;s.assign(a.default.vErrors,i._`${a.default.vErrors} === null ? ${t} : ${a.default.vErrors}.concat(${t})`),s.assign(a.default.errors,i._`${a.default.vErrors}.length`)}function m(e){var t;if(!c.opts.unevaluated)return;const r=null===(t=null==n?void 0:n.validate)||void 0===t?void 0:t.evaluated;if(!0!==c.props)if(r&&!r.dynamicProps)void 0!==r.props&&(c.props=l.mergeEvaluated.props(s,r.props,c.props));else{const t=s.var("props",i._`${e}.evaluated.props`);c.props=l.mergeEvaluated.props(s,t,c.props,i.Name)}if(!0!==c.items)if(r&&!r.dynamicItems)void 0!==r.items&&(c.items=l.mergeEvaluated.items(s,r.items,c.items));else{const t=s.var("items",i._`${e}.evaluated.items`);c.items=l.mergeEvaluated.items(s,t,c.items,i.Name)}}r?function(){if(!p.$async)throw new Error("async schema referenced by sync schema");const n=s.let("valid");s.try((()=>{s.code(i._`await ${o.callValidateCode(e,t,f)}`),m(t),u||s.assign(n,!0)}),(e=>{s.if(i._`!(${e} instanceof ${c.ValidationError})`,(()=>s.throw(e))),h(e),u||s.assign(n,!1)})),e.ok(n)}():function(){const n=s.name("visitedNodes");s.code(i._`const ${n} = visitedNodesForRef.get(${t}) || new Set()`),s.if(i._`!${n}.has(${e.data})`,(()=>{s.code(i._`visitedNodesForRef.set(${t}, ${n})`),s.code(i._`const dataNode = ${e.data}`),s.code(i._`${n}.add(dataNode)`);const r=e.result(o.callValidateCode(e,t,f),(()=>m(t)),(()=>h(t)));return s.code(i._`${n}.delete(dataNode)`),r}))}()}t.getValidate=u,t.callRef=p,t.default=c},5522:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6545),i={keyword:"discriminator",type:"object",schemaType:"object",error:{message:({params:{discrError:e,tagName:t}})=>e===o.DiscrError.Tag?`tag "${t}" must be string`:`value of tag "${t}" must be in oneOf`,params:({params:{discrError:e,tag:t,tagName:n}})=>r._`{error: ${e}, tag: ${n}, tagValue: ${t}}`},code(e){const{gen:t,data:n,schema:i,parentSchema:a,it:s}=e,{oneOf:l}=a;if(!s.opts.discriminator)throw new Error("discriminator: requires discriminator option");const c=i.propertyName;if("string"!=typeof c)throw new Error("discriminator: requires propertyName");if(!l)throw new Error("discriminator: requires oneOf keyword");const u=t.let("valid",!1),p=t.const("tag",r._`${n}${r.getProperty(c)}`);function d(n){const o=t.name("valid"),i=e.subschema({keyword:"oneOf",schemaProp:n},o);return e.mergeEvaluated(i,r.Name),o}function f(e){return e.hasOwnProperty("$ref")}t.if(r._`typeof ${p} == "string"`,(()=>function(){const n=function(){var e;const t={},n=o(a);let r=!0;for(let t=0;te.error(!1,{discrError:o.DiscrError.Tag,tag:p,tagName:c}))),e.ok(u)}};t.default=i},6545:function(e,t){"use strict";var n;Object.defineProperty(t,"__esModule",{value:!0}),t.DiscrError=void 0,(n=t.DiscrError||(t.DiscrError={})).Tag="tag",n.Mapping="mapping"},6479:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8223),o=n(3799),i=n(9616),a=n(3815),s=n(4826),l=[r.default,o.default,i.default(),a.default,s.metadataVocabulary,s.contentVocabulary];t.default=l},157:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:"format",type:["number","string"],schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>r.str`must match format "${e}"`,params:({schemaCode:e})=>r._`{format: ${e}}`},code(e,t){const{gen:n,data:o,$data:i,schema:a,schemaCode:s,it:l}=e,{opts:c,errSchemaPath:u,schemaEnv:p,self:d}=l;c.validateFormats&&(i?function(){const i=n.scopeValue("formats",{ref:d.formats,code:c.code.formats}),a=n.const("fDef",r._`${i}[${s}]`),l=n.let("fType"),u=n.let("format");n.if(r._`typeof ${a} == "object" && !(${a} instanceof RegExp)`,(()=>n.assign(l,r._`${a}.type || "string"`).assign(u,r._`${a}.validate`)),(()=>n.assign(l,r._`"string"`).assign(u,a))),e.fail$data(r.or(!1===c.strictSchema?r.nil:r._`${s} && !${u}`,function(){const e=p.$async?r._`(${a}.async ? await ${u}(${o}) : ${u}(${o}))`:r._`${u}(${o})`,n=r._`(typeof ${u} == "function" ? ${e} : ${u}.test(${o}))`;return r._`${u} && ${u} !== true && ${l} === ${t} && !${n}`}()))}():function(){const i=d.formats[a];if(!i)return void function(){if(!1!==c.strictSchema)throw new Error(e());function e(){return`unknown format "${a}" ignored in schema at path "${u}"`}d.logger.warn(e())}();if(!0===i)return;const[s,l,f]=function(e){const t=e instanceof RegExp?r.regexpCode(e):c.code.formats?r._`${c.code.formats}${r.getProperty(a)}`:void 0,o=n.scopeValue("formats",{key:a,ref:e,code:t});return"object"!=typeof e||e instanceof RegExp?["string",e,o]:[e.type||"string",e.validate,r._`${o}.validate`]}(i);s===t&&e.pass(function(){if("object"==typeof i&&!(i instanceof RegExp)&&i.async){if(!p.$async)throw new Error("async format in sync schema");return r._`await ${f}(${o})`}return"function"==typeof l?r._`${f}(${o})`:r._`${f}.test(${o})`}())}())}};t.default=o},3815:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=[n(157).default];t.default=r},4826:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.contentVocabulary=t.metadataVocabulary=void 0,t.metadataVocabulary=["title","description","default","deprecated","readOnly","writeOnly","examples"],t.contentVocabulary=["contentMediaType","contentEncoding","contentSchema"]},7535:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(412),a={keyword:"const",$data:!0,error:{message:"must be equal to constant",params:({schemaCode:e})=>r._`{allowedValue: ${e}}`},code(e){const{gen:t,data:n,$data:a,schemaCode:s,schema:l}=e;a||l&&"object"==typeof l?e.fail$data(r._`!${o.useFunc(t,i.default)}(${n}, ${s})`):e.fail(r._`${l} !== ${n}`)}};t.default=a},4147:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(412),a={keyword:"enum",schemaType:"array",$data:!0,error:{message:"must be equal to one of the allowed values",params:({schemaCode:e})=>r._`{allowedValues: ${e}}`},code(e){const{gen:t,data:n,$data:a,schema:s,schemaCode:l,it:c}=e;if(!a&&0===s.length)throw new Error("enum must have non-empty array");const u=s.length>=c.opts.loopEnum,p=o.useFunc(t,i.default);let d;if(u||a)d=t.let("valid"),e.block$data(d,(function(){t.assign(d,!1),t.forOf("v",l,(e=>t.if(r._`${p}(${n}, ${e})`,(()=>t.assign(d,!0).break()))))}));else{if(!Array.isArray(s))throw new Error("ajv implementation error");const e=t.const("vSchema",l);d=r.or(...s.map(((t,o)=>function(e,t){const o=s[t];return"object"==typeof o&&null!==o?r._`${p}(${n}, ${e}[${t}])`:r._`${n} === ${o}`}(e,o))))}e.pass(d)}};t.default=a},3799:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(9640),o=n(7692),i=n(3765),a=n(8582),s=n(6711),l=n(7835),c=n(8950),u=n(7326),p=n(7535),d=n(4147),f=[r.default,o.default,i.default,a.default,s.default,l.default,c.default,u.default,{keyword:"type",schemaType:["string","array"]},{keyword:"nullable",schemaType:"boolean"},p.default,d.default];t.default=f},8950:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:["maxItems","minItems"],type:"array",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxItems"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} items`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:o}=e,i="maxItems"===t?r.operators.GT:r.operators.LT;e.fail$data(r._`${n}.length ${i} ${o}`)}};t.default=o},3765:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=n(6124),i=n(5872),a={keyword:["maxLength","minLength"],type:"string",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxLength"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} characters`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:a,it:s}=e,l="maxLength"===t?r.operators.GT:r.operators.LT,c=!1===s.opts.unicode?r._`${n}.length`:r._`${o.useFunc(e.gen,i.default)}(${n})`;e.fail$data(r._`${c} ${l} ${a}`)}};t.default=a},9640:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o=r.operators,i={maximum:{okStr:"<=",ok:o.LTE,fail:o.GT},minimum:{okStr:">=",ok:o.GTE,fail:o.LT},exclusiveMaximum:{okStr:"<",ok:o.LT,fail:o.GTE},exclusiveMinimum:{okStr:">",ok:o.GT,fail:o.LTE}},a={message:({keyword:e,schemaCode:t})=>r.str`must be ${i[e].okStr} ${t}`,params:({keyword:e,schemaCode:t})=>r._`{comparison: ${i[e].okStr}, limit: ${t}}`},s={keyword:Object.keys(i),type:"number",schemaType:"number",$data:!0,error:a,code(e){const{keyword:t,data:n,schemaCode:o}=e;e.fail$data(r._`${n} ${i[t].fail} ${o} || isNaN(${n})`)}};t.default=s},6711:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:["maxProperties","minProperties"],type:"object",schemaType:"number",$data:!0,error:{message({keyword:e,schemaCode:t}){const n="maxProperties"===e?"more":"fewer";return r.str`must NOT have ${n} than ${t} items`},params:({schemaCode:e})=>r._`{limit: ${e}}`},code(e){const{keyword:t,data:n,schemaCode:o}=e,i="maxProperties"===t?r.operators.GT:r.operators.LT;e.fail$data(r._`Object.keys(${n}).length ${i} ${o}`)}};t.default=o},7692:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(4475),o={keyword:"multipleOf",type:"number",schemaType:"number",$data:!0,error:{message:({schemaCode:e})=>r.str`must be multiple of ${e}`,params:({schemaCode:e})=>r._`{multipleOf: ${e}}`},code(e){const{gen:t,data:n,schemaCode:o,it:i}=e,a=i.opts.multipleOfPrecision,s=t.let("res"),l=a?r._`Math.abs(Math.round(${s}) - ${s}) > 1e-${a}`:r._`${s} !== parseInt(${s})`;e.fail$data(r._`(${o} === 0 || (${s} = ${n}/${o}, ${l}))`)}};t.default=o},8582:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i={keyword:"pattern",type:"string",schemaType:"string",$data:!0,error:{message:({schemaCode:e})=>o.str`must match pattern "${e}"`,params:({schemaCode:e})=>o._`{pattern: ${e}}`},code(e){const{data:t,$data:n,schema:i,schemaCode:a,it:s}=e,l=s.opts.unicodeRegExp?"u":"",c=n?o._`(new RegExp(${a}, ${l}))`:r.usePattern(e,i);e.fail$data(o._`!${c}.test(${t})`)}};t.default=i},7835:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8619),o=n(4475),i=n(6124),a={keyword:"required",type:"object",schemaType:"array",$data:!0,error:{message:({params:{missingProperty:e}})=>o.str`must have required property '${e}'`,params:({params:{missingProperty:e}})=>o._`{missingProperty: ${e}}`},code(e){const{gen:t,schema:n,schemaCode:a,data:s,$data:l,it:c}=e,{opts:u}=c;if(!l&&0===n.length)return;const p=n.length>=u.loopRequired;if(c.allErrors?function(){if(p||l)e.block$data(o.nil,d);else for(const t of n)r.checkReportMissingProp(e,t)}():function(){const i=t.let("missing");if(p||l){const n=t.let("valid",!0);e.block$data(n,(()=>function(n,i){e.setParams({missingProperty:n}),t.forOf(n,a,(()=>{t.assign(i,r.propertyInData(t,s,n,u.ownProperties)),t.if(o.not(i),(()=>{e.error(),t.break()}))}),o.nil)}(i,n))),e.ok(n)}else t.if(r.checkMissingProp(e,n,i)),r.reportMissingProp(e,i),t.else()}(),u.strictRequired){const t=e.parentSchema.properties,{definedProperties:r}=e.it;for(const e of n)if(void 0===(null==t?void 0:t[e])&&!r.has(e)){const t=`required property "${e}" is not defined at "${c.schemaEnv.baseId+c.errSchemaPath}" (strictRequired)`;i.checkStrictMode(c,t,c.opts.strictRequired)}}function d(){t.forOf("prop",a,(n=>{e.setParams({missingProperty:n}),t.if(r.noPropertyInData(t,s,n,u.ownProperties),(()=>e.error()))}))}}};t.default=a},7326:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7927),o=n(4475),i=n(6124),a=n(412),s={keyword:"uniqueItems",type:"array",schemaType:"boolean",$data:!0,error:{message:({params:{i:e,j:t}})=>o.str`must NOT have duplicate items (items ## ${t} and ${e} are identical)`,params:({params:{i:e,j:t}})=>o._`{i: ${e}, j: ${t}}`},code(e){const{gen:t,data:n,$data:s,schema:l,parentSchema:c,schemaCode:u,it:p}=e;if(!s&&!l)return;const d=t.let("valid"),f=c.items?r.getSchemaTypes(c.items):[];function h(i,a){const s=t.name("item"),l=r.checkDataTypes(f,s,p.opts.strictNumbers,r.DataType.Wrong),c=t.const("indices",o._`{}`);t.for(o._`;${i}--;`,(()=>{t.let(s,o._`${n}[${i}]`),t.if(l,o._`continue`),f.length>1&&t.if(o._`typeof ${s} == "string"`,o._`${s} += "_"`),t.if(o._`typeof ${c}[${s}] == "number"`,(()=>{t.assign(a,o._`${c}[${s}]`),e.error(),t.assign(d,!1).break()})).code(o._`${c}[${s}] = ${i}`)}))}function m(r,s){const l=i.useFunc(t,a.default),c=t.name("outer");t.label(c).for(o._`;${r}--;`,(()=>t.for(o._`${s} = ${r}; ${s}--;`,(()=>t.if(o._`${l}(${n}[${r}], ${n}[${s}])`,(()=>{e.error(),t.assign(d,!1).break(c)}))))))}e.block$data(d,(function(){const r=t.let("i",o._`${n}.length`),i=t.let("j");e.setParams({i:r,j:i}),t.assign(d,!0),t.if(o._`${r} > 1`,(()=>(f.length>0&&!f.some((e=>"object"===e||"array"===e))?h:m)(r,i)))}),o._`${u} === false`),e.ok(d)}};t.default=s},4029:function(e){"use strict";var t=e.exports=function(e,t,r){"function"==typeof t&&(r=t,t={}),n(t,"function"==typeof(r=t.cb||r)?r:r.pre||function(){},r.post||function(){},e,"",e)};function n(e,r,o,i,a,s,l,c,u,p){if(i&&"object"==typeof i&&!Array.isArray(i)){for(var d in r(i,a,s,l,c,u,p),i){var f=i[d];if(Array.isArray(f)){if(d in t.arrayKeywords)for(var h=0;hn.addProblemToIgnore(e))),fileDependencies:o.getFiles(),rootType:S.DefinitionRoot,refTypes:A.refTypes,visitorsData:A.visitorsData}}))}function k(e,t){switch(t){case d.OasMajorVersion.Version3:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";case"Response":return"responses";case"Example":return"examples";case"RequestBody":return"requestBodies";case"Header":return"headers";case"SecuritySchema":return"securitySchemes";case"Link":return"links";case"Callback":return"callbacks";default:return null}case d.OasMajorVersion.Version2:switch(e){case"Schema":return"definitions";case"Parameter":return"parameters";case"Response":return"responses";default:return null}}}function _(e,t,n,r,a,s){let l;const c={ref:{leave(o,l,c){if(!c.location||void 0===c.node)return void m.reportUnresolvedRef(c,l.report,l.location);if(c.location.source===r.source&&c.location.source===l.location.source&&"scalar"!==l.type.name&&!t)return;if(n&&y.isRedoclyRegistryURL(o.$ref))return;if(s&&f.isAbsoluteUrl(o.$ref))return;const d=k(l.type.name,e);d?t?(p(d,c,l),u(o,c,l)):(o.$ref=p(d,c,l),function(e,t,n){const o=i.makeRefId(n.location.source.absoluteRef,e.$ref);a.set(o,{document:r,isRemote:!1,node:t.node,nodePointer:e.$ref,resolved:!0})}(o,c,l)):u(o,c,l)}},DefinitionRoot:{enter(t){e===d.OasMajorVersion.Version3?l=t.components=t.components||{}:e===d.OasMajorVersion.Version2&&(l=t)}}};function u(e,t,n){g.isPlainObject(t.node)?(delete e.$ref,Object.assign(e,t.node)):n.parent[n.key]=t.node}function p(t,n,r){l[t]=l[t]||{};const o=function(e,t,n){const[r,o]=[e.location.source.absoluteRef,e.location.pointer],i=l[t];let a="";const s=o.slice(2).split("/").filter(Boolean);for(;s.length>0;)if(a=s.pop()+(a?`-${a}`:""),!i||!i[a]||h(i[a],e,n))return a;if(a=f.refBaseName(r)+(a?`_${a}`:""),!i[a]||h(i[a],e,n))return a;const c=a;let u=2;for(;i[a]&&!h(i[a],e,n);)a=`${c}-${u}`,u++;return i[a]||n.report({message:`Two schemas are referenced with the same name but different content. Renamed ${c} to ${a}.`,location:n.location,forceSeverity:"warn"}),a}(n,t,r);return l[t][o]=n.node,e===d.OasMajorVersion.Version3?`#/components/${t}/${o}`:`#/${t}/${o}`}function h(e,t,n){var r;return!(!f.isRef(e)||(null===(r=n.resolve(e).location)||void 0===r?void 0:r.absolutePointer)!==t.location.absolutePointer)||o(e,t.node)}return e===d.OasMajorVersion.Version3&&(c.DiscriminatorMapping={leave(n,r){for(const o of Object.keys(n)){const i=n[o],a=r.resolve({$ref:i});if(!a.location||void 0===a.node)return void m.reportUnresolvedRef(a,r.report,r.location.child(o));const s=k("Schema",e);t?p(s,a,r):n[o]=p(s,a,r)}}}),c}!function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(w=t.OasVersion||(t.OasVersion={})),t.bundle=function(e){return r(this,void 0,void 0,(function*(){const{ref:t,doc:n,externalRefResolver:r=new i.BaseResolver(e.config.resolve),base:o=null}=e;if(!t&&!n)throw new Error("Document or reference is required.\n");const a=void 0!==n?n:yield r.resolveDocument(o,t,!0);if(a instanceof Error)throw a;return x(Object.assign(Object.assign({document:a},e),{config:e.config.lint,externalRefResolver:r}))}))},t.bundleDocument=x,t.mapTypeToComponent=k},6877:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"error","info-contact":"error","info-license":"error","info-license-url":"error","tag-description":"error","tags-alphabetical":"error","parameter-description":"error","no-identical-paths":"error","no-ambiguous-paths":"error","no-path-trailing-slash":"error","path-segment-plural":"error","path-declaration-must-exist":"error","path-not-include-query":"error","path-parameters-defined":"error","operation-description":"error","operation-2xx-response":"error","operation-4xx-response":"error",assertions:"error","operation-operationId":"error","operation-summary":"error","operation-operationId-unique":"error","operation-operationId-url-safe":"error","operation-parameters-unique":"error","operation-tag-defined":"error","operation-security-defined":"error","operation-singular-tag":"error","no-unresolved-refs":"error","no-enum-type-mismatch":"error","boolean-parameter-prefixes":"error","paths-kebab-case":"error","no-http-verbs-in-paths":"error","path-excludes-patterns":{severity:"error",patterns:[]},"request-mime-type":"error",spec:"error","no-invalid-schema-examples":"error","no-invalid-parameter-examples":"error","scalar-property-missing-example":"error"},oas3_0Rules:{"no-invalid-media-type-examples":"error","no-server-example.com":"error","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"error","no-undefined-server-variable":"error","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"error","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"error","no-undefined-server-variable":"error","no-servers-empty-enum":"error"}}},6242:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.defaultPlugin=t.builtInConfigs=void 0;const r=n(8057),o=n(6877),i=n(9016),a=n(226),s=n(7523),l=n(226),c=n(7523),u=n(1753),p=n(7060);t.builtInConfigs={recommended:r.default,minimal:i.default,all:o.default,"redocly-registry":{decorators:{"registry-dependencies":"on"}}},t.defaultPlugin={id:"",rules:{oas3:a.rules,oas2:s.rules},preprocessors:{oas3:l.preprocessors,oas2:c.preprocessors},decorators:{oas3:u.decorators,oas2:p.decorators},configs:t.builtInConfigs}},7040:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))},o=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o{if(p.isString(e)&&s.isAbsoluteUrl(e))throw new Error(a.red("We don't support remote plugins yet."));const o=p.isString(e)?n(i.resolve(i.dirname(t),e)):e,l=o.id;if("string"!=typeof l)throw new Error(a.red(`Plugin must define \`id\` property in ${a.blue(e.toString())}.`));if(r.has(l)){const t=r.get(l);throw new Error(a.red(`Plugin "id" must be unique. Plugin ${a.blue(e.toString())} uses id "${a.blue(l)}" already seen in ${a.blue(t)}`))}r.set(l,e.toString());const c=Object.assign(Object.assign({id:l},o.configs?{configs:o.configs}:{}),o.typeExtension?{typeExtension:o.typeExtension}:{});if(o.rules){if(!o.rules.oas3&&!o.rules.oas2)throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${e}.`);c.rules={},o.rules.oas3&&(c.rules.oas3=u.prefixRules(o.rules.oas3,l)),o.rules.oas2&&(c.rules.oas2=u.prefixRules(o.rules.oas2,l))}if(o.preprocessors){if(!o.preprocessors.oas3&&!o.preprocessors.oas2)throw new Error(`Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${e}.`);c.preprocessors={},o.preprocessors.oas3&&(c.preprocessors.oas3=u.prefixRules(o.preprocessors.oas3,l)),o.preprocessors.oas2&&(c.preprocessors.oas2=u.prefixRules(o.preprocessors.oas2,l))}if(o.decorators){if(!o.decorators.oas3&&!o.decorators.oas2)throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${e}.`);c.decorators={},o.decorators.oas3&&(c.decorators.oas3=u.prefixRules(o.decorators.oas3,l)),o.decorators.oas2&&(c.decorators.oas2=u.prefixRules(o.decorators.oas2,l))}return c})).filter(p.notUndefined)}function h({rawConfig:e,configPath:t="",resolver:n}){var o,i;return r(this,void 0,void 0,(function*(){const{apis:r={},lint:a={}}=e;let s={};for(const[e,l]of Object.entries(r||{})){if(null===(i=null===(o=l.lint)||void 0===o?void 0:o.extends)||void 0===i?void 0:i.some(p.isNotString))throw new Error("Error configuration format not detected in extends value must contain strings");const r=v(a,l.lint),c=yield g({lintConfig:r,configPath:t,resolver:n});s[e]=Object.assign(Object.assign({},l),{lint:c})}return s}))}function m({lintConfig:e,configPath:t="",resolver:n=new l.BaseResolver},a=[],d=[]){var h,g,v;return r(this,void 0,void 0,(function*(){if(a.includes(t))throw new Error(`Circular dependency in config file: "${t}"`);const l=u.getUniquePlugins(f([...(null==e?void 0:e.plugins)||[],c.defaultPlugin],t)),b=null===(h=null==e?void 0:e.plugins)||void 0===h?void 0:h.filter(p.isString).map((e=>i.resolve(i.dirname(t),e))),w=s.isAbsoluteUrl(t)?t:t&&i.resolve(t),x=yield Promise.all((null===(g=null==e?void 0:e.extends)||void 0===g?void 0:g.map((e=>r(this,void 0,void 0,(function*(){if(!s.isAbsoluteUrl(e)&&!i.extname(e))return y(e,l);const o=s.isAbsoluteUrl(e)?e:s.isAbsoluteUrl(t)?new URL(e,t).href:i.resolve(i.dirname(t),e),c=yield function(e,t){return r(this,void 0,void 0,(function*(){try{const n=yield t.loadExternalRef(e),r=u.transformConfig(p.parseYaml(n.body));if(!r.lint)throw new Error(`Lint configuration format not detected: "${e}"`);return r.lint}catch(t){throw new Error(`Failed to load "${e}": ${t.message}`)}}))}(o,n);return yield m({lintConfig:c,configPath:o,resolver:n},[...a,w],d)})))))||[]),k=u.mergeExtends([...x,Object.assign(Object.assign({},e),{plugins:l,extends:void 0,extendPaths:[...a,w],pluginPaths:b})]),{plugins:_=[]}=k,O=o(k,["plugins"]);return Object.assign(Object.assign({},O),{extendPaths:null===(v=O.extendPaths)||void 0===v?void 0:v.filter((e=>e&&!s.isAbsoluteUrl(e))),plugins:u.getUniquePlugins(_),recommendedFallback:null==e?void 0:e.recommendedFallback,doNotResolveExamples:null==e?void 0:e.doNotResolveExamples})}))}function g(e,t=[],n=[]){return r(this,void 0,void 0,(function*(){const r=yield m(e,t,n);return Object.assign(Object.assign({},r),{rules:r.rules&&b(r.rules)})}))}function y(e,t){var n;const{pluginId:r,configName:o}=u.parsePresetName(e),i=t.find((e=>e.id===r));if(!i)throw new Error(`Invalid config ${a.red(e)}: plugin ${r} is not included.`);const s=null===(n=i.configs)||void 0===n?void 0:n[o];if(!s)throw new Error(r?`Invalid config ${a.red(e)}: plugin ${r} doesn't export config with name ${o}.`:`Invalid config ${a.red(e)}: there is no such built-in config.`);return s}function v(e,t){return Object.assign(Object.assign(Object.assign({},e),t),{rules:Object.assign(Object.assign({},null==e?void 0:e.rules),null==t?void 0:t.rules),oas2Rules:Object.assign(Object.assign({},null==e?void 0:e.oas2Rules),null==t?void 0:t.oas2Rules),oas3_0Rules:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Rules),null==t?void 0:t.oas3_0Rules),oas3_1Rules:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Rules),null==t?void 0:t.oas3_1Rules),preprocessors:Object.assign(Object.assign({},null==e?void 0:e.preprocessors),null==t?void 0:t.preprocessors),oas2Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas2Preprocessors),null==t?void 0:t.oas2Preprocessors),oas3_0Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Preprocessors),null==t?void 0:t.oas3_0Preprocessors),oas3_1Preprocessors:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Preprocessors),null==t?void 0:t.oas3_1Preprocessors),decorators:Object.assign(Object.assign({},null==e?void 0:e.decorators),null==t?void 0:t.decorators),oas2Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas2Decorators),null==t?void 0:t.oas2Decorators),oas3_0Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas3_0Decorators),null==t?void 0:t.oas3_0Decorators),oas3_1Decorators:Object.assign(Object.assign({},null==e?void 0:e.oas3_1Decorators),null==t?void 0:t.oas3_1Decorators),recommendedFallback:!(null==t?void 0:t.extends)&&e.recommendedFallback})}function b(e){if(!e)return e;const t={},n=[];for(const[r,o]of Object.entries(e))if(r.startsWith("assert/")&&"object"==typeof o&&null!==o){const e=o;n.push(Object.assign(Object.assign({},e),{assertionId:r.replace("assert/","")}))}else t[r]=o;return n.length>0&&(t.assertions=n),t}t.resolveConfig=function(e,t){var n,o,i,a,s;return r(this,void 0,void 0,(function*(){if(null===(o=null===(n=e.lint)||void 0===n?void 0:n.extends)||void 0===o?void 0:o.some(p.isNotString))throw new Error("Error configuration format not detected in extends value must contain strings");const r=new l.BaseResolver(u.getResolveConfig(e.resolve)),c=null!==(a=null===(i=null==e?void 0:e.lint)||void 0===i?void 0:i.extends)&&void 0!==a?a:["recommended"],f=!(null===(s=null==e?void 0:e.lint)||void 0===s?void 0:s.extends),m=Object.assign(Object.assign({},null==e?void 0:e.lint),{extends:c,recommendedFallback:f}),y=yield h({rawConfig:Object.assign(Object.assign({},e),{lint:m}),configPath:t,resolver:r}),v=yield g({lintConfig:m,configPath:t,resolver:r});return new d.Config(Object.assign(Object.assign({},e),{apis:y,lint:v}),t)}))},t.resolvePlugins=f,t.resolveApis=h,t.resolveLint=g,t.resolvePreset=y},3777:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Config=t.LintConfig=t.AVAILABLE_REGIONS=t.DOMAINS=t.DEFAULT_REGION=t.IGNORE_FILE=t.env=void 0;const r=n(5101),o=n(6470),i=n(5273),a=n(771),s=n(1510),l=n(2565);t.env="undefined"!=typeof process&&{}||{},t.IGNORE_FILE=".redocly.lint-ignore.yaml",t.DEFAULT_REGION="us",t.DOMAINS=function(){const e={us:"redocly.com",eu:"eu.redocly.com"},n=t.env.REDOCLY_DOMAIN;return(null==n?void 0:n.endsWith(".redocly.host"))&&(e[n.split(".")[0]]=n),"redoc.online"===n&&(e[n]=n),e}(),t.AVAILABLE_REGIONS=Object.keys(t.DOMAINS);class c{constructor(e,n){this.rawConfig=e,this.configFile=n,this.ignore={},this._usedRules=new Set,this._usedVersions=new Set,this.plugins=e.plugins||[],this.doNotResolveExamples=!!e.doNotResolveExamples,this.recommendedFallback=e.recommendedFallback||!1,this.rules={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.rules),e.oas2Rules),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.rules),e.oas3_0Rules),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.rules),e.oas3_1Rules)},this.preprocessors={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.preprocessors),e.oas2Preprocessors),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.preprocessors),e.oas3_0Preprocessors),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.preprocessors),e.oas3_1Preprocessors)},this.decorators={[s.OasVersion.Version2]:Object.assign(Object.assign({},e.decorators),e.oas2Decorators),[s.OasVersion.Version3_0]:Object.assign(Object.assign({},e.decorators),e.oas3_0Decorators),[s.OasVersion.Version3_1]:Object.assign(Object.assign({},e.decorators),e.oas3_1Decorators)},this.extendPaths=e.extendPaths||[],this.pluginPaths=e.pluginPaths||[];const a=this.configFile?o.dirname(this.configFile):"undefined"!=typeof process&&process.cwd()||"",l=o.join(a,t.IGNORE_FILE);if(r.hasOwnProperty("existsSync")&&r.existsSync(l)){this.ignore=i.parseYaml(r.readFileSync(l,"utf-8"))||{};for(const e of Object.keys(this.ignore)){this.ignore[o.resolve(o.dirname(l),e)]=this.ignore[e];for(const t of Object.keys(this.ignore[e]))this.ignore[e][t]=new Set(this.ignore[e][t]);delete this.ignore[e]}}}saveIgnore(){const e=this.configFile?o.dirname(this.configFile):process.cwd(),n=o.join(e,t.IGNORE_FILE),s={};for(const t of Object.keys(this.ignore)){const n=s[a.slash(o.relative(e,t))]=this.ignore[t];for(const e of Object.keys(n))n[e]=Array.from(n[e])}r.writeFileSync(n,"# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n# See https://redoc.ly/docs/cli/ for more information.\n"+i.stringifyYaml(s))}addIgnore(e){const t=this.ignore,n=e.location[0];if(void 0===n.pointer)return;const r=t[n.source.absoluteRef]=t[n.source.absoluteRef]||{};(r[e.ruleId]=r[e.ruleId]||new Set).add(n.pointer)}addProblemToIgnore(e){const t=e.location[0];if(void 0===t.pointer)return e;const n=(this.ignore[t.source.absoluteRef]||{})[e.ruleId],r=n&&n.has(t.pointer);return r?Object.assign(Object.assign({},e),{ignored:r}):e}extendTypes(e,t){let n=e;for(const e of this.plugins)if(void 0!==e.typeExtension)switch(t){case s.OasVersion.Version3_0:case s.OasVersion.Version3_1:if(!e.typeExtension.oas3)continue;n=e.typeExtension.oas3(n,t);case s.OasVersion.Version2:if(!e.typeExtension.oas2)continue;n=e.typeExtension.oas2(n,t);default:throw new Error("Not implemented")}return n}getRuleSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.rules[t][e]||"off";return"string"==typeof n?{severity:n}:Object.assign({severity:"error"},n)}getPreprocessorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.preprocessors[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getDecoratorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.decorators[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getUnusedRules(){const e=[],t=[],n=[];for(const r of Array.from(this._usedVersions))e.push(...Object.keys(this.rules[r]).filter((e=>!this._usedRules.has(e)))),t.push(...Object.keys(this.decorators[r]).filter((e=>!this._usedRules.has(e)))),n.push(...Object.keys(this.preprocessors[r]).filter((e=>!this._usedRules.has(e))));return{rules:e,preprocessors:n,decorators:t}}getRulesForOasVersion(e){switch(e){case s.OasMajorVersion.Version3:const e=[];return this.plugins.forEach((t=>{var n;return(null===(n=t.preprocessors)||void 0===n?void 0:n.oas3)&&e.push(t.preprocessors.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.rules)||void 0===n?void 0:n.oas3)&&e.push(t.rules.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.decorators)||void 0===n?void 0:n.oas3)&&e.push(t.decorators.oas3)})),e;case s.OasMajorVersion.Version2:const t=[];return this.plugins.forEach((e=>{var n;return(null===(n=e.preprocessors)||void 0===n?void 0:n.oas2)&&t.push(e.preprocessors.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.rules)||void 0===n?void 0:n.oas2)&&t.push(e.rules.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.decorators)||void 0===n?void 0:n.oas2)&&t.push(e.decorators.oas2)})),t}}skipRules(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.rules[e][t]&&(this.rules[e][t]="off")}skipPreprocessors(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.preprocessors[e][t]&&(this.preprocessors[e][t]="off")}skipDecorators(e){for(const t of e||[])for(const e of Object.values(s.OasVersion))this.decorators[e][t]&&(this.decorators[e][t]="off")}}t.LintConfig=c,t.Config=class{constructor(e,t){this.rawConfig=e,this.configFile=t,this.apis=e.apis||{},this.lint=new c(e.lint||{},t),this["features.openapi"]=e["features.openapi"]||{},this["features.mockServer"]=e["features.mockServer"]||{},this.resolve=l.getResolveConfig(null==e?void 0:e.resolve),this.region=e.region,this.organization=e.organization}}},8698:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),o(n(3777),t),o(n(3865),t),o(n(5030),t),o(n(6242),t),o(n(9129),t),o(n(2565),t),o(n(7040),t)},9129:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.getConfig=t.findConfig=t.CONFIG_FILE_NAMES=t.loadConfig=void 0;const o=n(5101),i=n(6470),a=n(1094),s=n(771),l=n(3777),c=n(2565),u=n(7040);function p(e){if(!o.hasOwnProperty("existsSync"))return;const n=t.CONFIG_FILE_NAMES.map((t=>e?i.resolve(e,t):t)).filter(o.existsSync);if(n.length>1)throw new Error(`\n Multiple configuration files are not allowed. \n Found the following files: ${n.join(", ")}. \n Please use 'redocly.yaml' instead.\n `);return n[0]}function d(e=p()){return r(this,void 0,void 0,(function*(){if(!e)return{};try{const t=(yield s.loadYaml(e))||{};return c.transformConfig(t)}catch(t){throw new Error(`Error parsing config file at '${e}': ${t.message}`)}}))}t.loadConfig=function(e=p(),t,n){return r(this,void 0,void 0,(function*(){const o=yield d(e);return"function"==typeof n&&(yield n(o)),yield function({rawConfig:e,customExtends:t,configPath:n}){var o;return r(this,void 0,void 0,(function*(){void 0!==t?(e.lint=e.lint||{},e.lint.extends=t):s.isEmptyObject(e);const r=new a.RedoclyClient,i=yield r.getTokens();if(i.length){e.resolve||(e.resolve={}),e.resolve.http||(e.resolve.http={}),e.resolve.http.headers=[...null!==(o=e.resolve.http.headers)&&void 0!==o?o:[]];for(const t of i){const n=l.DOMAINS[t.region];e.resolve.http.headers.push({matches:`https://api.${n}/registry/**`,name:"Authorization",envVariable:void 0,value:t.token},..."us"===t.region?[{matches:"https://api.redoc.ly/registry/**",name:"Authorization",envVariable:void 0,value:t.token}]:[])}}return u.resolveConfig(e,n)}))}({rawConfig:o,customExtends:t,configPath:e})}))},t.CONFIG_FILE_NAMES=["redocly.yaml","redocly.yml",".redocly.yaml",".redocly.yml"],t.findConfig=p,t.getConfig=d},9016:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"warn","info-contact":"off","info-license":"off","info-license-url":"off","tag-description":"warn","tags-alphabetical":"off","parameter-description":"off","no-path-trailing-slash":"warn","no-identical-paths":"warn","no-ambiguous-paths":"warn","path-declaration-must-exist":"warn","path-not-include-query":"warn","path-parameters-defined":"warn","operation-description":"off","operation-2xx-response":"warn","operation-4xx-response":"off",assertions:"warn","operation-operationId":"warn","operation-summary":"warn","operation-operationId-unique":"warn","operation-parameters-unique":"warn","operation-tag-defined":"off","operation-security-defined":"warn","operation-operationId-url-safe":"warn","operation-singular-tag":"off","no-unresolved-refs":"error","no-enum-type-mismatch":"warn","boolean-parameter-prefixes":"off","paths-kebab-case":"off",spec:"error"},oas3_0Rules:{"no-invalid-media-type-examples":{severity:"warn",disallowAdditionalProperties:!0},"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"warn","no-example-value-and-externalValue":"warn","no-unused-components":"warn","no-undefined-server-variable":"warn","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"warn","no-example-value-and-externalValue":"warn","no-unused-components":"warn","no-undefined-server-variable":"warn","no-servers-empty-enum":"error"}}},8057:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={rules:{"info-description":"warn","info-contact":"off","info-license":"warn","info-license-url":"warn","tag-description":"warn","tags-alphabetical":"off","parameter-description":"off","no-path-trailing-slash":"error","no-identical-paths":"error","no-ambiguous-paths":"warn","path-declaration-must-exist":"error","path-not-include-query":"error","path-parameters-defined":"error","operation-description":"off","operation-2xx-response":"warn",assertions:"warn","operation-4xx-response":"warn","operation-operationId":"warn","operation-summary":"error","operation-operationId-unique":"error","operation-operationId-url-safe":"error","operation-parameters-unique":"error","operation-tag-defined":"off","operation-security-defined":"error","operation-singular-tag":"off","no-unresolved-refs":"error","no-enum-type-mismatch":"error","boolean-parameter-prefixes":"off","paths-kebab-case":"off",spec:"error"},oas3_0Rules:{"no-invalid-media-type-examples":{severity:"warn",disallowAdditionalProperties:!0},"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"warn","no-undefined-server-variable":"error","no-servers-empty-enum":"error"},oas3_1Rules:{"no-server-example.com":"warn","no-server-trailing-slash":"error","no-empty-servers":"error","no-example-value-and-externalValue":"error","no-unused-components":"warn","no-undefined-server-variable":"error","no-servers-empty-enum":"error"}}},5030:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.initRules=void 0;const r=n(771);t.initRules=function(e,t,n,o){return e.flatMap((e=>Object.keys(e).map((r=>{const i=e[r],a="rules"===n?t.getRuleSettings(r,o):"preprocessors"===n?t.getPreprocessorSettings(r,o):t.getDecoratorSettings(r,o);if("off"===a.severity)return;const s=i(a);return Array.isArray(s)?s.map((e=>({severity:a.severity,ruleId:r,visitor:e}))):{severity:a.severity,ruleId:r,visitor:s}})))).flatMap((e=>e)).filter(r.notUndefined)}},3865:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0})},2565:function(e,t,n){"use strict";var r=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o-1){const[t,n]=e.split("/");return{pluginId:t,configName:n}}return{pluginId:"",configName:e}},t.transformApiDefinitionsToApis=s,t.prefixRules=function(e,t){if(!t)return e;const n={};for(const r of Object.keys(e))n[`${t}/${r}`]=e[r];return n},t.mergeExtends=function(e){const t={rules:{},oas2Rules:{},oas3_0Rules:{},oas3_1Rules:{},preprocessors:{},oas2Preprocessors:{},oas3_0Preprocessors:{},oas3_1Preprocessors:{},decorators:{},oas2Decorators:{},oas3_0Decorators:{},oas3_1Decorators:{},plugins:[],pluginPaths:[],extendPaths:[]};for(let n of e){if(n.extends)throw new Error(`\`extends\` is not supported in shared configs yet: ${JSON.stringify(n,null,2)}.`);Object.assign(t.rules,n.rules),Object.assign(t.oas2Rules,n.oas2Rules),i.assignExisting(t.oas2Rules,n.rules||{}),Object.assign(t.oas3_0Rules,n.oas3_0Rules),i.assignExisting(t.oas3_0Rules,n.rules||{}),Object.assign(t.oas3_1Rules,n.oas3_1Rules),i.assignExisting(t.oas3_1Rules,n.rules||{}),Object.assign(t.preprocessors,n.preprocessors),Object.assign(t.oas2Preprocessors,n.oas2Preprocessors),i.assignExisting(t.oas2Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_0Preprocessors,n.oas3_0Preprocessors),i.assignExisting(t.oas3_0Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_1Preprocessors,n.oas3_1Preprocessors),i.assignExisting(t.oas3_1Preprocessors,n.preprocessors||{}),Object.assign(t.decorators,n.decorators),Object.assign(t.oas2Decorators,n.oas2Decorators),i.assignExisting(t.oas2Decorators,n.decorators||{}),Object.assign(t.oas3_0Decorators,n.oas3_0Decorators),i.assignExisting(t.oas3_0Decorators,n.decorators||{}),Object.assign(t.oas3_1Decorators,n.oas3_1Decorators),i.assignExisting(t.oas3_1Decorators,n.decorators||{}),t.plugins.push(...n.plugins||[]),t.pluginPaths.push(...n.pluginPaths||[]),t.extendPaths.push(...new Set(n.extendPaths))}return t},t.getMergedConfig=function(e,t){var n,r,o,i,s,l;const c=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.lint)||void 0===t?void 0:t.extendPaths})),null===(r=null===(n=e.rawConfig)||void 0===n?void 0:n.lint)||void 0===r?void 0:r.extendPaths].flat().filter(Boolean),u=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.lint)||void 0===t?void 0:t.pluginPaths})),null===(i=null===(o=e.rawConfig)||void 0===o?void 0:o.lint)||void 0===i?void 0:i.pluginPaths].flat().filter(Boolean);return t?new a.Config(Object.assign(Object.assign({},e.rawConfig),{lint:Object.assign(Object.assign({},e.apis[t]?e.apis[t].lint:e.rawConfig.lint),{extendPaths:c,pluginPaths:u}),"features.openapi":Object.assign(Object.assign({},e["features.openapi"]),null===(s=e.apis[t])||void 0===s?void 0:s["features.openapi"]),"features.mockServer":Object.assign(Object.assign({},e["features.mockServer"]),null===(l=e.apis[t])||void 0===l?void 0:l["features.mockServer"])}),e.configFile):e},t.transformConfig=function(e){if(e.apis&&e.apiDefinitions)throw new Error("Do not use 'apiDefinitions' field. Use 'apis' instead.\n");if(e["features.openapi"]&&e.referenceDocs)throw new Error("Do not use 'referenceDocs' field. Use 'features.openapi' instead.\n");const t=e,{apiDefinitions:n,referenceDocs:i}=t,a=r(t,["apiDefinitions","referenceDocs"]);return n&&process.stderr.write(`The ${o.yellow("apiDefinitions")} field is deprecated. Use ${o.green("apis")} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`),i&&process.stderr.write(`The ${o.yellow("referenceDocs")} field is deprecated. Use ${o.green("features.openapi")} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`),Object.assign({"features.openapi":i,apis:s(n)},a)},t.getResolveConfig=function(e){var t,n;return{http:{headers:null!==(n=null===(t=null==e?void 0:e.http)||void 0===t?void 0:t.headers)&&void 0!==n?n:[],customFetch:void 0}}},t.getUniquePlugins=function(e){const t=new Set,n=[];for(const r of e)t.has(r.id)?r.id&&process.stderr.write(`Duplicate plugin id "${o.yellow(r.id)}".\n`):(n.push(r),t.add(r.id));return n}},1988:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.checkIfMatchByStrategy=t.filter=void 0;const r=n(7468),o=n(771);function i(e){return Array.isArray(e)?e:[e]}t.filter=function(e,t,n){const{parent:i,key:a}=t;let s=!1;if(Array.isArray(e))for(let o=0;oe.includes(t))):"all"===n&&t.every((t=>e.includes(t)))):e===t)}},9244:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FilterIn=void 0;const r=n(1988);t.FilterIn=({property:e,value:t,matchStrategy:n})=>{const o=n||"any",i=n=>(null==n?void 0:n[e])&&!r.checkIfMatchByStrategy(null==n?void 0:n[e],t,o);return{any:{enter:(e,t)=>{r.filter(e,t,i)}}}}},8623:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FilterOut=void 0;const r=n(1988);t.FilterOut=({property:e,value:t,matchStrategy:n})=>{const o=n||"any",i=n=>r.checkIfMatchByStrategy(null==n?void 0:n[e],t,o);return{any:{enter:(e,t)=>{r.filter(e,t,i)}}}}},4555:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoDescriptionOverride=void 0;const r=n(771);t.InfoDescriptionOverride=({filePath:e})=>({Info:{leave(t,{report:n,location:o}){if(!e)throw new Error('Parameter "filePath" is not provided for "info-description-override" rule');try{t.description=r.readFileAsStringSync(e)}catch(e){n({message:`Failed to read markdown override file for "info.description".\n${e.message}`,location:o.child("description")})}}}})},7802:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationDescriptionOverride=void 0;const r=n(771);t.OperationDescriptionOverride=({operationIds:e})=>({Operation:{leave(t,{report:n,location:o}){if(!t.operationId)return;if(!e)throw new Error('Parameter "operationIds" is not provided for "operation-description-override" rule');const i=t.operationId;if(e[i])try{t.description=r.readFileAsStringSync(e[i])}catch(e){n({message:`Failed to read markdown override file for operation "${i}".\n${e.message}`,location:o.child("operationId").key()})}}}})},2287:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryDependencies=void 0;const r=n(1094);t.RegistryDependencies=()=>{let e=new Set;return{DefinitionRoot:{leave(t,n){n.getVisitorData().links=Array.from(e)}},ref(t){if(t.$ref){const n=t.$ref.split("#/")[0];r.isRedoclyRegistryURL(n)&&e.add(n)}}}}},5830:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveXInternal=void 0;const r=n(771),o=n(7468);t.RemoveXInternal=({internalFlagProperty:e})=>{const t=e||"x-internal";return{any:{enter:(e,n)=>{!function(e,n){var i,a,s,l;const{parent:c,key:u}=n;let p=!1;if(Array.isArray(e))for(let r=0;r({Tag:{leave(t,{report:n}){if(!e)throw new Error('Parameter "tagNames" is not provided for "tag-description-override" rule');if(e[t.name])try{t.description=r.readFileAsStringSync(e[t.name])}catch(e){n({message:`Failed to read markdown override file for tag "${t.name}".\n${e.message}`})}}}})},7060:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decorators=void 0;const r=n(2287),o=n(7802),i=n(423),a=n(4555),s=n(5830),l=n(9244),c=n(8623);t.decorators={"registry-dependencies":r.RegistryDependencies,"operation-description-override":o.OperationDescriptionOverride,"tag-description-override":i.TagDescriptionOverride,"info-description-override":a.InfoDescriptionOverride,"remove-x-internal":s.RemoveXInternal,"filter-in":l.FilterIn,"filter-out":c.FilterOut}},1753:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decorators=void 0;const r=n(2287),o=n(7802),i=n(423),a=n(4555),s=n(5830),l=n(9244),c=n(8623);t.decorators={"registry-dependencies":r.RegistryDependencies,"operation-description-override":o.OperationDescriptionOverride,"tag-description-override":i.TagDescriptionOverride,"info-description-override":a.InfoDescriptionOverride,"remove-x-internal":s.RemoveXInternal,"filter-in":l.FilterIn,"filter-out":c.FilterOut}},5273:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.stringifyYaml=t.parseYaml=void 0;const r=n(3320),o=r.JSON_SCHEMA.extend({implicit:[r.types.merge],explicit:[r.types.binary,r.types.omap,r.types.pairs,r.types.set]});t.parseYaml=(e,t)=>r.load(e,Object.assign({schema:o},t)),t.stringifyYaml=(e,t)=>r.dump(e,t)},1510:function(e,t){"use strict";var n,r;Object.defineProperty(t,"__esModule",{value:!0}),t.openAPIMajor=t.detectOpenAPI=t.OasMajorVersion=t.OasVersion=void 0,function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(n=t.OasVersion||(t.OasVersion={})),function(e){e.Version2="oas2",e.Version3="oas3"}(r=t.OasMajorVersion||(t.OasMajorVersion={})),t.detectOpenAPI=function(e){if("object"!=typeof e)throw new Error("Document must be JSON object, got "+typeof e);if(!e.openapi&&!e.swagger)throw new Error("This doesn’t look like an OpenAPI document.\n");if(e.openapi&&"string"!=typeof e.openapi)throw new Error(`Invalid OpenAPI version: should be a string but got "${typeof e.openapi}"`);if(e.openapi&&e.openapi.startsWith("3.0"))return n.Version3_0;if(e.openapi&&e.openapi.startsWith("3.1"))return n.Version3_1;if(e.swagger&&"2.0"===e.swagger)return n.Version2;throw new Error(`Unsupported OpenAPI Version: ${e.openapi||e.swagger}`)},t.openAPIMajor=function(e){return e===n.Version2?r.Version2:r.Version3}},1094:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isRedoclyRegistryURL=t.RedoclyClient=void 0;const o=n(2116),i=n(6470),a=n(6918),s=n(8836),l=n(1390),c=n(3777),u=n(771),p=".redocly-config.json";t.RedoclyClient=class{constructor(e){this.accessTokens={},this.region=this.loadRegion(e),this.loadTokens(),this.domain=e?c.DOMAINS[e]:c.env.REDOCLY_DOMAIN||c.DOMAINS[c.DEFAULT_REGION],c.env.REDOCLY_DOMAIN=this.domain,this.registryApi=new l.RegistryApi(this.accessTokens,this.region)}loadRegion(e){if(e&&!c.DOMAINS[e])throw new Error(`Invalid argument: region in config file.\nGiven: ${s.green(e)}, choices: "us", "eu".`);return c.env.REDOCLY_DOMAIN?c.AVAILABLE_REGIONS.find((e=>c.DOMAINS[e]===c.env.REDOCLY_DOMAIN))||c.DEFAULT_REGION:e||c.DEFAULT_REGION}getRegion(){return this.region}hasTokens(){return u.isNotEmptyObject(this.accessTokens)}hasToken(){return!!this.accessTokens[this.region]}getAuthorizationHeader(){return r(this,void 0,void 0,(function*(){return this.accessTokens[this.region]}))}setAccessTokens(e){this.accessTokens=e}loadTokens(){const e=i.resolve(a.homedir(),p),t=this.readCredentialsFile(e);u.isNotEmptyObject(t)&&this.setAccessTokens(Object.assign(Object.assign({},t),t.token&&!t[this.region]&&{[this.region]:t.token})),c.env.REDOCLY_AUTHORIZATION&&this.setAccessTokens(Object.assign(Object.assign({},this.accessTokens),{[this.region]:c.env.REDOCLY_AUTHORIZATION}))}getAllTokens(){return Object.entries(this.accessTokens).filter((([e])=>c.AVAILABLE_REGIONS.includes(e))).map((([e,t])=>({region:e,token:t})))}getValidTokens(){return r(this,void 0,void 0,(function*(){const e=this.getAllTokens(),t=yield Promise.allSettled(e.map((({token:e,region:t})=>this.verifyToken(e,t))));return e.filter(((e,n)=>"fulfilled"===t[n].status)).map((({token:e,region:t})=>({token:e,region:t,valid:!0})))}))}getTokens(){return r(this,void 0,void 0,(function*(){return this.hasTokens()?yield this.getValidTokens():[]}))}isAuthorizedWithRedoclyByRegion(){return r(this,void 0,void 0,(function*(){if(!this.hasTokens())return!1;const e=this.accessTokens[this.region];if(!e)return!1;try{return yield this.verifyToken(e,this.region),!0}catch(e){return!1}}))}isAuthorizedWithRedocly(){return r(this,void 0,void 0,(function*(){return this.hasTokens()&&u.isNotEmptyObject(yield this.getValidTokens())}))}readCredentialsFile(e){return o.existsSync(e)?JSON.parse(o.readFileSync(e,"utf-8")):{}}verifyToken(e,t,n=!1){return r(this,void 0,void 0,(function*(){return this.registryApi.authStatus(e,t,n)}))}login(e,t=!1){return r(this,void 0,void 0,(function*(){const n=i.resolve(a.homedir(),p);try{yield this.verifyToken(e,this.region,t)}catch(e){throw new Error("Authorization failed. Please check if you entered a valid API key.")}const r=Object.assign(Object.assign({},this.readCredentialsFile(n)),{[this.region]:e,token:e});this.accessTokens=r,this.registryApi.setAccessTokens(r),o.writeFileSync(n,JSON.stringify(r,null,2))}))}logout(){const e=i.resolve(a.homedir(),p);o.existsSync(e)&&o.unlinkSync(e)}},t.isRedoclyRegistryURL=function(e){const t=c.env.REDOCLY_DOMAIN||c.DOMAINS[c.DEFAULT_REGION],n="redocly.com"===t?"redoc.ly":t;return!(!e.startsWith(`https://api.${t}/registry/`)&&!e.startsWith(`https://api.${n}/registry/`))}},1390:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryApi=void 0;const o=n(8150),i=n(3777),a=n(771),s=n(3244).i8;t.RegistryApi=class{constructor(e,t){this.accessTokens=e,this.region=t}get accessToken(){return a.isNotEmptyObject(this.accessTokens)&&this.accessTokens[this.region]}getBaseUrl(e=i.DEFAULT_REGION){return`https://api.${i.DOMAINS[e]}/registry`}setAccessTokens(e){return this.accessTokens=e,this}request(e="",t={},n){return r(this,void 0,void 0,(function*(){const r=Object.assign({},t.headers||{},{"x-redocly-cli-version":s});if(!r.hasOwnProperty("authorization"))throw new Error("Unauthorized");const i=yield o.default(`${this.getBaseUrl(n)}${e}`,Object.assign({},t,{headers:r}));if(401===i.status)throw new Error("Unauthorized");if(404===i.status){const e=yield i.json();throw new Error(e.code)}return i}))}authStatus(e,t,n=!1){return r(this,void 0,void 0,(function*(){try{const n=yield this.request("",{headers:{authorization:e}},t);return yield n.json()}catch(e){throw n&&console.log(e),e}}))}prepareFileUpload({organizationId:e,name:t,version:n,filesHash:o,filename:i,isUpsert:a}){return r(this,void 0,void 0,(function*(){const r=yield this.request(`/${e}/${t}/${n}/prepare-file-upload`,{method:"POST",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({filesHash:o,filename:i,isUpsert:a})},this.region);if(r.ok)return r.json();throw new Error("Could not prepare file upload")}))}pushApi({organizationId:e,name:t,version:n,rootFilePath:o,filePaths:i,branch:a,isUpsert:s,isPublic:l,batchId:c,batchSize:u}){return r(this,void 0,void 0,(function*(){if(!(yield this.request(`/${e}/${t}/${n}`,{method:"PUT",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({rootFilePath:o,filePaths:i,branch:a,isUpsert:s,isPublic:l,batchId:c,batchSize:u})},this.region)).ok)throw new Error("Could not push api")}))}}},7468:function(e,t){"use strict";function n(e,t){return""===e&&(e="#/"),"/"===e[e.length-1]?e+t:e+"/"+t}Object.defineProperty(t,"__esModule",{value:!0}),t.isMappingRef=t.isAbsoluteUrl=t.refBaseName=t.pointerBaseName=t.parsePointer=t.parseRef=t.escapePointer=t.unescapePointer=t.Location=t.isRef=t.joinPointer=void 0,t.joinPointer=n,t.isRef=function(e){return e&&"string"==typeof e.$ref};class r{constructor(e,t){this.source=e,this.pointer=t}child(e){return new r(this.source,n(this.pointer,(Array.isArray(e)?e:[e]).map(i).join("/")))}key(){return Object.assign(Object.assign({},this),{reportOnKey:!0})}get absolutePointer(){return this.source.absoluteRef+("#/"===this.pointer?"":this.pointer)}}function o(e){return decodeURIComponent(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function i(e){return"number"==typeof e?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}t.Location=r,t.unescapePointer=o,t.escapePointer=i,t.parseRef=function(e){const[t,n]=e.split("#/");return{uri:t||null,pointer:n?n.split("/").map(o).filter(Boolean):[]}},t.parsePointer=function(e){return e.substr(2).split("/").map(o)},t.pointerBaseName=function(e){const t=e.split("/");return t[t.length-1]},t.refBaseName=function(e){const t=e.split(/[\/\\]/);return t[t.length-1].replace(/\.[^.]+$/,"")},t.isAbsoluteUrl=function(e){return e.startsWith("http://")||e.startsWith("https://")},t.isMappingRef=function(e){return e.startsWith("#")||e.startsWith("https://")||e.startsWith("http://")||e.startsWith("./")||e.startsWith("../")||e.indexOf("/")>-1}},4182:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.resolveDocument=t.BaseResolver=t.makeDocumentFromString=t.makeRefId=t.YamlParseError=t.ResolveError=t.Source=void 0;const o=n(3197),i=n(6470),a=n(7468),s=n(5220),l=n(771);class c{constructor(e,t,n){this.absoluteRef=e,this.body=t,this.mimeType=n}getAst(e){var t;return void 0===this._ast&&(this._ast=null!==(t=e(this.body,{filename:this.absoluteRef}))&&void 0!==t?t:void 0,this._ast&&0===this._ast.kind&&""===this._ast.value&&1!==this._ast.startPosition&&(this._ast.startPosition=1,this._ast.endPosition=1)),this._ast}getLines(){return void 0===this._lines&&(this._lines=this.body.split(/\r\n|[\n\r]/g)),this._lines}}t.Source=c;class u extends Error{constructor(e){super(e.message),this.originalError=e,Object.setPrototypeOf(this,u.prototype)}}t.ResolveError=u;const p=/\((\d+):(\d+)\)$/;class d extends Error{constructor(e,t){super(e.message.split("\n")[0]),this.originalError=e,this.source=t,Object.setPrototypeOf(this,d.prototype);const[,n,r]=this.message.match(p)||[];this.line=parseInt(n,10),this.col=parseInt(r,10)}}function f(e,t){return e+"::"+t}function h(e,t){return{prev:e,node:t}}t.YamlParseError=d,t.makeRefId=f,t.makeDocumentFromString=function(e,t){const n=new c(t,e);try{return{source:n,parsed:l.parseYaml(e,{filename:t})}}catch(e){throw new d(e,n)}},t.BaseResolver=class{constructor(e={http:{headers:[]}}){this.config=e,this.cache=new Map}getFiles(){return new Set(Array.from(this.cache.keys()))}resolveExternalRef(e,t){return a.isAbsoluteUrl(t)?t:e&&a.isAbsoluteUrl(e)?new URL(t,e).href:i.resolve(e?i.dirname(e):process.cwd(),t)}loadExternalRef(e){return r(this,void 0,void 0,(function*(){try{if(a.isAbsoluteUrl(e)){const{body:t,mimeType:n}=yield l.readFileFromUrl(e,this.config.http);return new c(e,t,n)}return new c(e,yield o.promises.readFile(e,"utf-8"))}catch(e){throw new u(e)}}))}parseDocument(e,t=!1){var n;const r=e.absoluteRef.substr(e.absoluteRef.lastIndexOf("."));if(![".json",".json",".yml",".yaml"].includes(r)&&!(null===(n=e.mimeType)||void 0===n?void 0:n.match(/(json|yaml|openapi)/))&&!t)return{source:e,parsed:e.body};try{return{source:e,parsed:l.parseYaml(e.body,{filename:e.absoluteRef})}}catch(t){throw new d(t,e)}}resolveDocument(e,t,n=!1){return r(this,void 0,void 0,(function*(){const r=this.resolveExternalRef(e,t),o=this.cache.get(r);if(o)return o;const i=this.loadExternalRef(r).then((e=>this.parseDocument(e,n)));return this.cache.set(r,i),i}))}};const m={name:"unknown",properties:{}},g={name:"scalar",properties:{}};t.resolveDocument=function(e){return r(this,void 0,void 0,(function*(){const{rootDocument:t,externalRefResolver:n,rootType:o}=e,i=new Map,l=new Set,c=[];let u;!function e(t,o,u,p){function d(e,t,o){return r(this,void 0,void 0,(function*(){if(function(e,t){for(;e;){if(e.node===t)return!0;e=e.prev}return!1}(o.prev,t))throw new Error("Self-referencing circular pointer");const{uri:r,pointer:s}=a.parseRef(t.$ref),l=null!==r;let c;try{c=l?yield n.resolveDocument(e.source.absoluteRef,r):e}catch(n){const r={resolved:!1,isRemote:l,document:void 0,error:n},o=f(e.source.absoluteRef,t.$ref);return i.set(o,r),r}let u={resolved:!0,document:c,isRemote:l,node:e.parsed,nodePointer:"#/"},p=c.parsed;const m=s;for(let e of m){if("object"!=typeof p){p=void 0;break}if(void 0!==p[e])p=p[e],u.nodePointer=a.joinPointer(u.nodePointer,a.escapePointer(e));else{if(!a.isRef(p)){p=void 0;break}if(u=yield d(c,p,h(o,p)),c=u.document||c,"object"!=typeof u.node){p=void 0;break}p=u.node[e],u.nodePointer=a.joinPointer(u.nodePointer,a.escapePointer(e))}}u.node=p,u.document=c;const g=f(e.source.absoluteRef,t.$ref);return u.document&&a.isRef(p)&&(u=yield d(u.document,p,h(o,p))),i.set(g,u),Object.assign({},u)}))}!function t(n,r,i){if("object"!=typeof n||null===n)return;const u=`${r.name}::${i}`;if(!l.has(u))if(l.add(u),Array.isArray(n)){const e=r.items;if(r!==m&&void 0===e)return;for(let r=0;r{t.resolved&&e(t.node,t.document,t.nodePointer,r)}));c.push(t)}}}(t,p,o.source.absoluteRef+u)}(t.parsed,t,"#/",o);do{u=yield Promise.all(c)}while(c.length!==u.length);return i}))}},7275:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateJsonSchema=t.releaseAjvInstance=void 0;const r=n(5499),o=n(7468);let i=null;t.releaseAjvInstance=function(){i=null},t.validateJsonSchema=function(e,t,n,a,s,l){const c=function(e,t,n,o){const a=function(e,t){return i||(i=new r.default({schemaId:"$id",meta:!0,allErrors:!0,strictSchema:!1,inlineRefs:!1,validateSchema:!1,discriminator:!0,allowUnionTypes:!0,validateFormats:!1,defaultAdditionalProperties:!t,loadSchemaSync(t,n){const r=e({$ref:n},t.split("#")[0]);return!(!r||!r.location)&&Object.assign({$id:r.location.absolutePointer},r.node)},logger:!1})),i}(n,o);return a.getSchema(t.absolutePointer)||a.addSchema(Object.assign({$id:t.absolutePointer},e),t.absolutePointer),a.getSchema(t.absolutePointer)}(t,n,s,l);return c?{valid:!!c(e,{instancePath:a,parentData:{fake:{}},parentDataProperty:"fake",rootData:{},dynamicAnchors:{}}),errors:(c.errors||[]).map((function(e){let t=e.message,n="enum"===e.keyword?e.params.allowedValues:void 0;n&&(t+=` ${n.map((e=>`"${e}"`)).join(", ")}`),"type"===e.keyword&&(t=`type ${t}`);const r=e.instancePath.substring(a.length+1),i=r.substring(r.lastIndexOf("/")+1);if(i&&(t=`\`${i}\` property ${t}`),"additionalProperties"===e.keyword){const n=e.params.additionalProperty;t=`${t} \`${n}\``,e.instancePath+="/"+o.escapePointer(n)}return Object.assign(Object.assign({},e),{message:t,suggest:n})}))}:{valid:!0,errors:[]}}},9740:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.asserts=t.runOnValuesSet=t.runOnKeysSet=void 0;const r=n(771),o=n(5738);t.runOnKeysSet=new Set(["mutuallyExclusive","mutuallyRequired","enum","pattern","minLength","maxLength","casing","sortOrder","disallowed","required","requireAny","ref"]),t.runOnValuesSet=new Set(["pattern","enum","defined","undefined","nonEmpty","minLength","maxLength","casing","sortOrder","ref"]),t.asserts={pattern:(e,t,n)=>{if(void 0===e)return{isValid:!0};const i=r.isString(e)?[e]:e,a=o.regexFromString(t);for(let t of i)if(!(null==a?void 0:a.test(t)))return{isValid:!1,location:r.isString(e)?n:n.key()};return{isValid:!0}},enum:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o)if(!t.includes(i))return{isValid:!1,location:r.isString(e)?n:n.child(i).key()};return{isValid:!0}},defined:(e,t=!0,n)=>{const r=void 0!==e;return{isValid:t?r:!r,location:n}},required:(e,t,n)=>{for(const r of t)if(!e.includes(r))return{isValid:!1,location:n.key()};return{isValid:!0}},disallowed:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o)if(t.includes(i))return{isValid:!1,location:r.isString(e)?n:n.child(i).key()};return{isValid:!0}},undefined:(e,t=!0,n)=>{const r=void 0===e;return{isValid:t?r:!r,location:n}},nonEmpty:(e,t=!0,n)=>{const r=null==e||""===e;return{isValid:t?!r:r,location:n}},minLength:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:e.length>=t,location:n},maxLength:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:e.length<=t,location:n},casing:(e,t,n)=>{if(void 0===e)return{isValid:!0};const o=r.isString(e)?[e]:e;for(let i of o){let o=!1;switch(t){case"camelCase":o=!!i.match(/^[a-z][a-zA-Z0-9]+$/g);break;case"kebab-case":o=!!i.match(/^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g);break;case"snake_case":o=!!i.match(/^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g);break;case"PascalCase":o=!!i.match(/^[A-Z][a-zA-Z0-9]+$/g);break;case"MACRO_CASE":o=!!i.match(/^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g);break;case"COBOL-CASE":o=!!i.match(/^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g);break;case"flatcase":o=!!i.match(/^[a-z][a-z0-9]+$/g)}if(!o)return{isValid:!1,location:r.isString(e)?n:n.child(i).key()}}return{isValid:!0}},sortOrder:(e,t,n)=>void 0===e?{isValid:!0}:{isValid:o.isOrdered(e,t),location:n},mutuallyExclusive:(e,t,n)=>({isValid:o.getIntersectionLength(e,t)<2,location:n.key()}),mutuallyRequired:(e,t,n)=>({isValid:!(o.getIntersectionLength(e,t)>0)||o.getIntersectionLength(e,t)===t.length,location:n.key()}),requireAny:(e,t,n)=>({isValid:o.getIntersectionLength(e,t)>=1,location:n.key()}),ref:(e,t,n,r)=>{if(void 0===r)return{isValid:!0};const i=r.hasOwnProperty("$ref");if("boolean"==typeof t)return{isValid:t?i:!i,location:i?n:n.key()};const a=o.regexFromString(t);return{isValid:i&&(null==a?void 0:a.test(r.$ref)),location:i?n:n.key()}}}},4015:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Assertions=void 0;const r=n(9740),o=n(5738);t.Assertions=e=>{let t=[];const n=Object.values(e).filter((e=>"object"==typeof e&&null!==e));for(const[e,i]of n.entries()){const n=i.assertionId&&`${i.assertionId} assertion`||`assertion #${e+1}`;if(!i.subject)throw new Error(`${n}: 'subject' is required`);const a=Array.isArray(i.subject)?i.subject:[i.subject],s=Object.keys(r.asserts).filter((e=>void 0!==i[e])).map((e=>({assertId:n,name:e,conditions:i[e],message:i.message,severity:i.severity||"error",suggest:i.suggest||[],runsOnKeys:r.runOnKeysSet.has(e),runsOnValues:r.runOnValuesSet.has(e)}))),l=s.find((e=>e.runsOnKeys&&!e.runsOnValues)),c=s.find((e=>e.runsOnValues&&!e.runsOnKeys));if(c&&!i.property)throw new Error(`${c.name} can't be used on all keys. Please provide a single property.`);if(l&&i.property)throw new Error(`${l.name} can't be used on a single property. Please use 'property'.`);for(const e of a){const n=o.buildSubjectVisitor(i.property,s,i.context),r=o.buildVisitorObject(e,i.context,n);t.push(r)}}return t}},5738:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.regexFromString=t.isOrdered=t.getIntersectionLength=t.buildSubjectVisitor=t.buildVisitorObject=void 0;const r=n(7468),o=n(9740);function i({values:e,rawValues:t,assert:n,location:r,report:i}){const a=o.asserts[n.name](e,n.conditions,r,t);a.isValid||i({message:n.message||`The ${n.assertId} doesn't meet required conditions`,location:a.location||r,forceSeverity:n.severity,suggest:n.suggest,ruleId:n.assertId})}t.buildVisitorObject=function(e,t,n){if(!t)return{[e]:n};let r={};const o=r;for(let n=0;ni?!i.includes(t):a?a.includes(t):void 0}:{},r=r[o.type]}return r[e]=n,o},t.buildSubjectVisitor=function(e,t,n){return(o,{report:a,location:s,rawLocation:l,key:c,type:u,resolve:p,rawNode:d})=>{var f;if(n){const e=n[n.length-1];if(e.type===u.name){const t=e.matchParentKeys,n=e.excludeParentKeys;if(t&&!t.includes(c))return;if(n&&n.includes(c))return}}e&&(e=Array.isArray(e)?e:[e]);for(const n of t){const t="ref"===n.name?l:s;if(e)for(const s of e)i({values:r.isRef(o[s])?null===(f=p(o[s]))||void 0===f?void 0:f.node:o[s],rawValues:d[s],assert:n,location:t.child(s),report:a});else{const e="ref"===n.name?d:Object.keys(o);i({values:Object.keys(o),rawValues:e,assert:n,location:t,report:a})}}}},t.getIntersectionLength=function(e,t){const n=new Set(t);let r=0;for(const t of e)n.has(t)&&r++;return r},t.isOrdered=function(e,t){const n=t.direction||t,r=t.property;for(let t=1;t=i:o<=i))return!1}return!0},t.regexFromString=function(e){const t=e.match(/^\/(.*)\/(.*)|(.*)/);return t&&new RegExp(t[1]||t[3],t[2])}},8265:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoContact=void 0;const r=n(780);t.InfoContact=()=>({Info(e,{report:t,location:n}){e.contact||t({message:r.missingRequiredField("Info","contact"),location:n.child("contact").key()})}})},8675:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoDescription=void 0;const r=n(780);t.InfoDescription=()=>({Info(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},9622:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoLicense=void 0;const r=n(780);t.InfoLicense=()=>({Info(e,{report:t}){e.license||t({message:r.missingRequiredField("Info","license"),location:{reportOnKey:!0}})}})},476:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.InfoLicenseUrl=void 0;const r=n(780);t.InfoLicenseUrl=()=>({License(e,t){r.validateDefinedAndNonEmpty("url",e,t)}})},3467:function(e,t){"use strict";function n(e,t){const n=e.split("/"),r=t.split("/");if(n.length!==r.length)return!1;let o=0,i=0,a=!0;for(let e=0;e({PathMap(e,{report:t,location:r}){const o=[];for(const i of Object.keys(e)){const e=o.find((e=>n(e,i)));e&&t({message:`Paths should resolve unambiguously. Found two ambiguous paths: \`${e}\` and \`${i}\`.`,location:r.child([i]).key()}),o.push(i)}}})},2319:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoEnumTypeMismatch=void 0;const r=n(780);t.NoEnumTypeMismatch=()=>({Schema(e,{report:t,location:n}){if(!e.enum||Array.isArray(e.enum)){if(e.enum&&e.type&&!Array.isArray(e.type)){const o=e.enum.filter((t=>!r.matchesJsonSchemaType(t,e.type,e.nullable)));for(const i of o)t({message:`All values of \`enum\` field must be of the same type as the \`type\` field: expected "${e.type}" but received "${r.oasTypeOf(i)}".`,location:n.child(["enum",e.enum.indexOf(i)])})}if(e.enum&&e.type&&Array.isArray(e.type)){const o={};for(const t of e.enum){o[t]=[];for(const n of e.type)r.matchesJsonSchemaType(t,n,e.nullable)||o[t].push(n);o[t].length!==e.type.length&&delete o[t]}for(const r of Object.keys(o))t({message:`Enum value \`${r}\` must be of one type. Allowed types: \`${e.type}\`.`,location:n.child(["enum",e.enum.indexOf(r)])})}}}})},525:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoHttpVerbsInPaths=void 0;const r=n(771),o=["get","head","post","put","patch","delete","options","trace"];t.NoHttpVerbsInPaths=({splitIntoWords:e})=>({PathItem(t,{key:n,report:i,location:a}){const s=n.toString();if(!s.startsWith("/"))return;const l=s.split("/");for(const t of l){if(!t||r.isPathParameter(t))continue;const n=n=>e?r.splitCamelCaseIntoWords(t).has(n):t.toLocaleLowerCase().includes(n);for(const e of o)n(e)&&i({message:`path \`${s}\` should not contain http verb ${e}`,location:a.key()})}}})},4628:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoIdenticalPaths=void 0,t.NoIdenticalPaths=()=>({PathMap(e,{report:t,location:n}){const r=new Map;for(const o of Object.keys(e)){const e=o.replace(/{.+?}/g,"{VARIABLE}"),i=r.get(e);i?t({message:`The path already exists which differs only by path parameter name(s): \`${i}\` and \`${o}\`.`,location:n.child([o]).key()}):r.set(e,o)}}})},1562:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoInvalidParameterExamples=void 0;const r=n(780);t.NoInvalidParameterExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{Parameter:{leave(e,t){if(e.example&&r.validateExample(e.example,e.schema,t.location.child("example"),t,n),e.examples)for(const[n,o]of Object.entries(e.examples))"value"in o&&r.validateExample(o.value,e.schema,t.location.child(["examples",n]),t,!1)}}}}},78:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoInvalidSchemaExamples=void 0;const r=n(780);t.NoInvalidSchemaExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{Schema:{leave(e,t){if(e.examples)for(const o of e.examples)r.validateExample(o,e,t.location.child(["examples",e.examples.indexOf(o)]),t,n);e.example&&r.validateExample(e.example,e,t.location.child("example"),t,!1)}}}}},700:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoPathTrailingSlash=void 0,t.NoPathTrailingSlash=()=>({PathItem(e,{report:t,key:n,location:r}){n.endsWith("/")&&"/"!==n&&t({message:`\`${n}\` should not have a trailing slash.`,location:r.key()})}})},5946:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Operation2xxResponse=void 0,t.Operation2xxResponse=()=>({ResponsesMap(e,{report:t}){Object.keys(e).some((e=>"default"===e||/2[Xx0-9]{2}/.test(e)))||t({message:"Operation must have at least one `2xx` response.",location:{reportOnKey:!0}})}})},5281:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Operation4xxResponse=void 0,t.Operation4xxResponse=()=>({ResponsesMap(e,{report:t}){Object.keys(e).some((e=>/4[Xx0-9]{2}/.test(e)))||t({message:"Operation must have at least one `4xx` response.",location:{reportOnKey:!0}})}})},3408:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationDescription=void 0;const r=n(780);t.OperationDescription=()=>({Operation(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},8742:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationIdUnique=void 0,t.OperationIdUnique=()=>{const e=new Set;return{Operation(t,{report:n,location:r}){t.operationId&&(e.has(t.operationId)&&n({message:"Every operation must have a unique `operationId`.",location:r.child([t.operationId])}),e.add(t.operationId))}}}},5064:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationIdUrlSafe=void 0;const n=/^[A-Za-z0-9-._~:/?#\[\]@!\$&'()*+,;=]*$/;t.OperationIdUrlSafe=()=>({Operation(e,{report:t,location:r}){e.operationId&&!n.test(e.operationId)&&t({message:"Operation `operationId` should not have URL invalid characters.",location:r.child(["operationId"])})}})},8786:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationOperationId=void 0;const r=n(780);t.OperationOperationId=()=>({DefinitionRoot:{PathItem:{Operation(e,t){r.validateDefinedAndNonEmpty("operationId",e,t)}}}})},4112:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationParametersUnique=void 0,t.OperationParametersUnique=()=>{let e,t;return{PathItem:{enter(){e=new Set},Parameter(t,{report:n,key:r,parentLocations:o}){const i=`${t.in}___${t.name}`;e.has(i)&&n({message:`Paths must have unique \`name\` + \`in\` parameters.\nRepeats of \`in:${t.in}\` + \`name:${t.name}\`.`,location:o.PathItem.child(["parameters",r])}),e.add(`${t.in}___${t.name}`)},Operation:{enter(){t=new Set},Parameter(e,{report:n,key:r,parentLocations:o}){const i=`${e.in}___${e.name}`;t.has(i)&&n({message:`Operations must have unique \`name\` + \`in\` parameters. Repeats of \`in:${e.in}\` + \`name:${e.name}\`.`,location:o.Operation.child(["parameters",r])}),t.add(i)}}}}}},7892:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSecurityDefined=void 0,t.OperationSecurityDefined=()=>{let e=new Map;return{DefinitionRoot:{leave(t,{report:n}){for(const[t,r]of e.entries())if(!r.defined)for(const e of r.from)n({message:`There is no \`${t}\` security scheme defined.`,location:e.key()})}},SecurityScheme(t,{key:n}){e.set(n.toString(),{defined:!0,from:[]})},SecurityRequirement(t,{location:n}){for(const r of Object.keys(t)){const t=e.get(r),o=n.child([r]);t?t.from.push(o):e.set(r,{from:[o]})}}}}},8613:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSingularTag=void 0,t.OperationSingularTag=()=>({Operation(e,{report:t,location:n}){e.tags&&e.tags.length>1&&t({message:"Operation `tags` object should have only one tag.",location:n.child(["tags"]).key()})}})},9578:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationSummary=void 0;const r=n(780);t.OperationSummary=()=>({Operation(e,t){r.validateDefinedAndNonEmpty("summary",e,t)}})},5097:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OperationTagDefined=void 0,t.OperationTagDefined=()=>{let e;return{DefinitionRoot(t){var n;e=new Set((null!==(n=t.tags)&&void 0!==n?n:[]).map((e=>e.name)))},Operation(t,{report:n,location:r}){if(t.tags)for(let o=0;o({Parameter(e,{report:t,location:n}){void 0===e.description?t({message:"Parameter object description must be present.",location:{reportOnKey:!0}}):e.description||t({message:"Parameter object description must be non-empty string.",location:n.child(["description"])})}})},7890:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathDeclarationMustExist=void 0,t.PathDeclarationMustExist=()=>({PathItem(e,{report:t,key:n}){-1!==n.indexOf("{}")&&t({message:"Path parameter declarations must be non-empty. `{}` is invalid.",location:{reportOnKey:!0}})}})},3689:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathExcludesPatterns=void 0,t.PathExcludesPatterns=({patterns:e})=>({PathItem(t,{report:n,key:r,location:o}){if(!e)throw new Error('Parameter "patterns" is not provided for "path-excludes-patterns" rule');const i=r.toString();if(i.startsWith("/")){const t=e.filter((e=>i.match(e)));for(const e of t)n({message:`path \`${i}\` should not match regex pattern: \`${e}\``,location:o.key()})}}})},2332:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathHttpVerbsOrder=void 0;const n=["get","head","post","put","patch","delete","options","trace"];t.PathHttpVerbsOrder=e=>{const t=e&&e.order||n;if(!Array.isArray(t))throw new Error("path-http-verbs-order `order` option must be an array");return{PathItem(e,{report:n,location:r}){const o=Object.keys(e).filter((e=>t.includes(e)));for(let e=0;e({PathMap:{PathItem(e,{report:t,key:n}){n.toString().includes("?")&&t({message:"Don't put query string items in the path, they belong in parameters with `in: query`.",location:{reportOnKey:!0}})}}})},7421:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathParamsDefined=void 0;const n=/\{([a-zA-Z0-9_.-]+)\}+/g;t.PathParamsDefined=()=>{let e,t,r;return{PathItem:{enter(o,{key:i}){t=new Set,r=i,e=new Set(Array.from(i.toString().matchAll(n)).map((e=>e[1])))},Parameter(n,{report:o,location:i}){"path"===n.in&&n.name&&(t.add(n.name),e.has(n.name)||o({message:`Path parameter \`${n.name}\` is not used in the path \`${r}\`.`,location:i.child(["name"])}))},Operation:{leave(n,{report:o,location:i}){for(const n of Array.from(e.keys()))t.has(n)||o({message:`The operation does not define the path parameter \`{${n}}\` expected by path \`${r}\`.`,location:i.child(["parameters"]).key()})},Parameter(n,{report:o,location:i}){"path"===n.in&&n.name&&(t.add(n.name),e.has(n.name)||o({message:`Path parameter \`${n.name}\` is not used in the path \`${r}\`.`,location:i.child(["name"])}))}}}}}},3807:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathSegmentPlural=void 0;const r=n(771);t.PathSegmentPlural=e=>{const{ignoreLastPathSegment:t,exceptions:n}=e;return{PathItem:{leave(e,{report:o,key:i,location:a}){const s=i.toString();if(s.startsWith("/")){const e=s.split("/");e.shift(),t&&e.length>1&&e.pop();for(const t of e)n&&n.includes(t)||!r.isPathParameter(t)&&r.isSingular(t)&&o({message:`path segment \`${t}\` should be plural.`,location:a.key()})}}}}}},9527:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PathsKebabCase=void 0,t.PathsKebabCase=()=>({PathItem(e,{report:t,key:n}){n.substr(1).split("/").filter((e=>""!==e)).every((e=>/^{.+}$/.test(e)||/^[a-z0-9-.]+$/.test(e)))||t({message:`\`${n}\` does not use kebab-case.`,location:{reportOnKey:!0}})}})},5839:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsHeader=void 0;const r=n(771);t.ResponseContainsHeader=e=>{const t=e.names||{};return{Operation:{Response:{enter:(e,{report:n,location:o,key:i})=>{var a;const s=t[i]||t[r.getMatchingStatusCodeRange(i)]||t[r.getMatchingStatusCodeRange(i).toLowerCase()]||[];for(const t of s)(null===(a=e.headers)||void 0===a?void 0:a[t])||n({message:`Response object must contain a "${t}" header.`,location:o.child("headers").key()})}}}}}},5669:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ScalarPropertyMissingExample=void 0;const r=n(1510),o=["string","integer","number","boolean","null"];t.ScalarPropertyMissingExample=()=>({SchemaProperties(e,{report:t,location:n,oasVersion:i,resolve:a}){for(const l of Object.keys(e)){const c=a(e[l]).node;c&&((s=c).type&&!(s.allOf||s.anyOf||s.oneOf)&&"binary"!==s.format&&(Array.isArray(s.type)?s.type.every((e=>o.includes(e))):o.includes(s.type)))&&void 0===c.example&&void 0===c.examples&&t({message:`Scalar property should have "example"${i===r.OasVersion.Version3_1?' or "examples"':""} defined.`,location:n.child(l).key()})}var s}})},6471:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OasSpec=void 0;const r=n(5220),o=n(780),i=n(7468),a=n(771);t.OasSpec=()=>({any(e,{report:t,type:n,location:s,key:l,resolve:c,ignoreNextVisitorsOnNode:u}){var p,d,f,h;const m=o.oasTypeOf(e);if(n.items)return void("array"!==m&&(t({message:`Expected type \`${n.name}\` (array) but got \`${m}\``}),u()));if("object"!==m)return t({message:`Expected type \`${n.name}\` (object) but got \`${m}\``}),void u();const g="function"==typeof n.required?n.required(e,l):n.required;for(let n of g||[])e.hasOwnProperty(n)||t({message:`The field \`${n}\` must be present on this level.`,location:[{reportOnKey:!0}]});const y=null===(p=n.allowed)||void 0===p?void 0:p.call(n,e);if(y&&a.isPlainObject(e))for(const r in e)y.includes(r)||n.extensionsPrefix&&r.startsWith(n.extensionsPrefix)||!Object.keys(n.properties).includes(r)||t({message:`The field \`${r}\` is not allowed here.`,location:s.child([r]).key()});const v=n.requiredOneOf||null;if(v){let r=!1;for(let t of v||[])e.hasOwnProperty(t)&&(r=!0);r||t({message:`Must contain at least one of the following fields: ${null===(d=n.requiredOneOf)||void 0===d?void 0:d.join(", ")}.`,location:[{reportOnKey:!0}]})}for(const a of Object.keys(e)){const l=s.child([a]);let u=e[a],p=n.properties[a];if(void 0===p&&(p=n.additionalProperties),"function"==typeof p&&(p=p(u,a)),r.isNamedType(p))continue;const d=p,m=o.oasTypeOf(u);if(void 0!==d){if(null!==d){if(!1!==d.resolvable&&i.isRef(u)&&(u=c(u).node),d.enum)d.enum.includes(u)||t({location:l,message:`\`${a}\` can be one of the following only: ${d.enum.map((e=>`"${e}"`)).join(", ")}.`,suggest:o.getSuggest(u,d.enum)});else if(d.type&&!o.matchesJsonSchemaType(u,d.type,!1))t({message:`Expected type \`${d.type}\` but got \`${m}\`.`,location:l});else if("array"===m&&(null===(f=d.items)||void 0===f?void 0:f.type)){const e=null===(h=d.items)||void 0===h?void 0:h.type;for(let n=0;ne[a]&&t({message:`The value of the ${a} field must be greater than or equal to ${d.minimum}`,location:s.child([a])})}}else{if(a.startsWith("x-"))continue;t({message:`Property \`${a}\` is not expected here.`,suggest:o.getSuggest(a,Object.keys(n.properties)),location:l.key()})}}}})},7281:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TagDescription=void 0;const r=n(780);t.TagDescription=()=>({Tag(e,t){r.validateDefinedAndNonEmpty("description",e,t)}})},6855:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.TagsAlphabetical=void 0,t.TagsAlphabetical=()=>({DefinitionRoot(e,{report:t,location:n}){if(e.tags)for(let r=0;re.tags[r+1].name&&t({message:"The `tags` array should be in alphabetical order.",location:n.child(["tags",r])})}})},348:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportUnresolvedRef=t.NoUnresolvedRefs=void 0;const r=n(4182);function o(e,t,n){var o;const i=e.error;i instanceof r.YamlParseError&&t({message:"Failed to parse: "+i.message,location:{source:i.source,pointer:void 0,start:{col:i.col,line:i.line}}});const a=null===(o=e.error)||void 0===o?void 0:o.message;t({location:n,message:"Can't resolve $ref"+(a?": "+a:"")})}t.NoUnresolvedRefs=()=>({ref:{leave(e,{report:t,location:n},r){void 0===r.node&&o(r,t,n)}},DiscriminatorMapping(e,{report:t,resolve:n,location:r}){for(const i of Object.keys(e)){const a=n({$ref:e[i]});if(void 0!==a.node)return;o(a,t,r.child(i))}}}),t.reportUnresolvedRef=o},9566:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BooleanParameterPrefixes=void 0,t.BooleanParameterPrefixes=e=>{const t=e.prefixes||["is","has"],n=new RegExp(`^(${t.join("|")})[A-Z-_]`),r=t.map((e=>`\`${e}\``)),o=1===r.length?r[0]:r.slice(0,-1).join(", ")+" or "+r[t.length-1];return{Parameter(e,{report:t,location:r}){"boolean"!==e.type||n.test(e.name)||t({message:`Boolean parameter \`${e.name}\` should have ${o} prefix.`,location:r.child("name")})}}}},7523:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.preprocessors=t.rules=void 0;const r=n(6471),o=n(78),i=n(1562),a=n(8675),s=n(8265),l=n(9622),c=n(476),u=n(9566),p=n(7281),d=n(6855),f=n(9527),h=n(2319),m=n(700),g=n(5946),y=n(5281),v=n(4015),b=n(8742),w=n(4112),x=n(7421),k=n(5097),_=n(7890),O=n(5064),S=n(3408),E=n(5023),P=n(3529),A=n(8613),$=n(7892),C=n(348),R=n(2332),j=n(4628),T=n(8786),I=n(9578),N=n(3467),D=n(525),L=n(3689),M=n(7028),F=n(1750),z=n(3807),U=n(5839),V=n(7899),B=n(5669);t.rules={spec:r.OasSpec,"no-invalid-schema-examples":o.NoInvalidSchemaExamples,"no-invalid-parameter-examples":i.NoInvalidParameterExamples,"info-description":a.InfoDescription,"info-contact":s.InfoContact,"info-license":l.InfoLicense,"info-license-url":c.InfoLicenseUrl,"tag-description":p.TagDescription,"tags-alphabetical":d.TagsAlphabetical,"paths-kebab-case":f.PathsKebabCase,"no-enum-type-mismatch":h.NoEnumTypeMismatch,"boolean-parameter-prefixes":u.BooleanParameterPrefixes,"no-path-trailing-slash":m.NoPathTrailingSlash,"operation-2xx-response":g.Operation2xxResponse,"operation-4xx-response":y.Operation4xxResponse,assertions:v.Assertions,"operation-operationId-unique":b.OperationIdUnique,"operation-parameters-unique":w.OperationParametersUnique,"path-parameters-defined":x.PathParamsDefined,"operation-tag-defined":k.OperationTagDefined,"path-declaration-must-exist":_.PathDeclarationMustExist,"operation-operationId-url-safe":O.OperationIdUrlSafe,"operation-operationId":T.OperationOperationId,"operation-summary":I.OperationSummary,"operation-description":S.OperationDescription,"path-not-include-query":E.PathNotIncludeQuery,"path-params-defined":x.PathParamsDefined,"parameter-description":P.ParameterDescription,"operation-singular-tag":A.OperationSingularTag,"operation-security-defined":$.OperationSecurityDefined,"no-unresolved-refs":C.NoUnresolvedRefs,"no-identical-paths":j.NoIdenticalPaths,"no-ambiguous-paths":N.NoAmbiguousPaths,"path-http-verbs-order":R.PathHttpVerbsOrder,"no-http-verbs-in-paths":D.NoHttpVerbsInPaths,"path-excludes-patterns":L.PathExcludesPatterns,"request-mime-type":M.RequestMimeType,"response-mime-type":F.ResponseMimeType,"path-segment-plural":z.PathSegmentPlural,"response-contains-header":U.ResponseContainsHeader,"response-contains-property":V.ResponseContainsProperty,"scalar-property-missing-example":B.ScalarPropertyMissingExample},t.preprocessors={}},4508:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(771);t.RemoveUnusedComponents=()=>{let e=new Map;function t(t,n,r){var o;e.set(t.absolutePointer,{used:(null===(o=e.get(t.absolutePointer))||void 0===o?void 0:o.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:o}){if(["Schema","Parameter","Response","SecurityScheme"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString()})}}},DefinitionRoot:{leave(t,n){const o=n.getVisitorData();o.removedCount=0;let i=new Set;e.forEach((e=>{const{used:n,name:r,componentType:a}=e;!n&&a&&(i.add(a),delete t[a][r],o.removedCount++)}));for(const e of i)r.isEmptyObject(t[e])&&delete t[e]}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"definitions",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedSecuritySchemes:{SecurityScheme(e,{location:n,key:r}){t(n,"securityDefinitions",r.toString())}}}}},7028:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RequestMimeType=void 0;const r=n(771);t.RequestMimeType=({allowedValues:e})=>({DefinitionRoot(t,n){r.validateMimeType({type:"consumes",value:t},n,e)},Operation:{leave(t,n){r.validateMimeType({type:"consumes",value:t},n,e)}}})},7899:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsProperty=void 0;const r=n(771);t.ResponseContainsProperty=e=>{const t=e.names||{};let n;return{Operation:{Response:{skip:(e,t)=>"204"==`${t}`,enter:(e,t)=>{n=t.key},Schema(e,{report:o,location:i}){var a;if("object"!==e.type)return;const s=t[n]||t[r.getMatchingStatusCodeRange(n)]||t[r.getMatchingStatusCodeRange(n).toLowerCase()]||[];for(const t of s)(null===(a=e.properties)||void 0===a?void 0:a[t])||o({message:`Response object must contain a top-level "${t}" property.`,location:i.child("properties").key()})}}}}}},1750:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseMimeType=void 0;const r=n(771);t.ResponseMimeType=({allowedValues:e})=>({DefinitionRoot(t,n){r.validateMimeType({type:"produces",value:t},n,e)},Operation:{leave(t,n){r.validateMimeType({type:"produces",value:t},n,e)}}})},962:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BooleanParameterPrefixes=void 0,t.BooleanParameterPrefixes=e=>{const t=e.prefixes||["is","has"],n=new RegExp(`^(${t.join("|")})[A-Z-_]`),r=t.map((e=>`\`${e}\``)),o=1===r.length?r[0]:r.slice(0,-1).join(", ")+" or "+r[t.length-1];return{Parameter:{Schema(e,{report:t,parentLocations:r},i){"boolean"!==e.type||n.test(i.Parameter.name)||t({message:`Boolean parameter \`${i.Parameter.name}\` should have ${o} prefix.`,location:r.Parameter.child(["name"])})}}}}},226:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.preprocessors=t.rules=void 0;const r=n(6471),o=n(5946),i=n(5281),a=n(4015),s=n(8742),l=n(4112),c=n(7421),u=n(5097),p=n(1265),d=n(2319),f=n(700),h=n(7890),m=n(5064),g=n(6855),y=n(5486),v=n(2947),b=n(8675),w=n(7281),x=n(8265),k=n(9622),_=n(3408),O=n(897),S=n(5023),E=n(3529),P=n(8613),A=n(476),$=n(7892),C=n(348),R=n(962),j=n(9527),T=n(2332),I=n(7020),N=n(9336),D=n(4628),L=n(6208),M=n(8786),F=n(9578),z=n(3467),U=n(472),V=n(525),B=n(3736),q=n(503),W=n(3807),H=n(3689),Y=n(78),K=n(1562),G=n(5839),Q=n(7557),X=n(5669);t.rules={spec:r.OasSpec,"info-description":b.InfoDescription,"info-contact":x.InfoContact,"info-license":k.InfoLicense,"info-license-url":A.InfoLicenseUrl,"operation-2xx-response":o.Operation2xxResponse,"operation-4xx-response":i.Operation4xxResponse,assertions:a.Assertions,"operation-operationId-unique":s.OperationIdUnique,"operation-parameters-unique":l.OperationParametersUnique,"path-parameters-defined":c.PathParamsDefined,"operation-tag-defined":u.OperationTagDefined,"no-example-value-and-externalValue":p.NoExampleValueAndExternalValue,"no-enum-type-mismatch":d.NoEnumTypeMismatch,"no-path-trailing-slash":f.NoPathTrailingSlash,"no-empty-servers":I.NoEmptyServers,"path-declaration-must-exist":h.PathDeclarationMustExist,"operation-operationId-url-safe":m.OperationIdUrlSafe,"operation-operationId":M.OperationOperationId,"operation-summary":F.OperationSummary,"tags-alphabetical":g.TagsAlphabetical,"no-server-example.com":y.NoServerExample,"no-server-trailing-slash":v.NoServerTrailingSlash,"tag-description":w.TagDescription,"operation-description":_.OperationDescription,"no-unused-components":O.NoUnusedComponents,"path-not-include-query":S.PathNotIncludeQuery,"path-params-defined":c.PathParamsDefined,"parameter-description":E.ParameterDescription,"operation-singular-tag":P.OperationSingularTag,"operation-security-defined":$.OperationSecurityDefined,"no-unresolved-refs":C.NoUnresolvedRefs,"paths-kebab-case":j.PathsKebabCase,"boolean-parameter-prefixes":R.BooleanParameterPrefixes,"path-http-verbs-order":T.PathHttpVerbsOrder,"no-invalid-media-type-examples":N.ValidContentExamples,"no-identical-paths":D.NoIdenticalPaths,"no-ambiguous-paths":z.NoAmbiguousPaths,"no-undefined-server-variable":L.NoUndefinedServerVariable,"no-servers-empty-enum":U.NoEmptyEnumServers,"no-http-verbs-in-paths":V.NoHttpVerbsInPaths,"path-excludes-patterns":H.PathExcludesPatterns,"request-mime-type":B.RequestMimeType,"response-mime-type":q.ResponseMimeType,"path-segment-plural":W.PathSegmentPlural,"no-invalid-schema-examples":Y.NoInvalidSchemaExamples,"no-invalid-parameter-examples":K.NoInvalidParameterExamples,"response-contains-header":G.ResponseContainsHeader,"response-contains-property":Q.ResponseContainsProperty,"scalar-property-missing-example":X.ScalarPropertyMissingExample},t.preprocessors={}},7020:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoEmptyServers=void 0,t.NoEmptyServers=()=>({DefinitionRoot(e,{report:t,location:n}){e.hasOwnProperty("servers")?Array.isArray(e.servers)&&0!==e.servers.length||t({message:"Servers must be a non-empty array.",location:n.child(["servers"]).key()}):t({message:"Servers must be present.",location:n.child(["openapi"]).key()})}})},1265:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoExampleValueAndExternalValue=void 0,t.NoExampleValueAndExternalValue=()=>({Example(e,{report:t,location:n}){e.value&&e.externalValue&&t({message:"Example object can have either `value` or `externalValue` fields.",location:n.child(["value"]).key()})}})},9336:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ValidContentExamples=void 0;const r=n(7468),o=n(780);t.ValidContentExamples=e=>{var t;const n=null===(t=e.disallowAdditionalProperties)||void 0===t||t;return{MediaType:{leave(e,t){const{location:i,resolve:a}=t;if(e.schema)if(e.example)s(e.example,i.child("example"));else if(e.examples)for(const t of Object.keys(e.examples))s(e.examples[t],i.child(["examples",t,"value"]),!0);function s(i,s,l){if(r.isRef(i)){const e=a(i);if(!e.location)return;s=l?e.location.child("value"):e.location,i=e.node}o.validateExample(l?i.value:i,e.schema,s,t,n)}}}}}},5486:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoServerExample=void 0,t.NoServerExample=()=>({Server(e,{report:t,location:n}){-1!==["example.com","localhost"].indexOf(e.url)&&t({message:"Server `url` should not point at example.com.",location:n.child(["url"])})}})},2947:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoServerTrailingSlash=void 0,t.NoServerTrailingSlash=()=>({Server(e,{report:t,location:n}){e.url&&e.url.endsWith("/")&&"/"!==e.url&&t({message:"Server `url` should not have a trailing slash.",location:n.child(["url"])})}})},472:function(e,t){"use strict";var n;function r(e){var t;if(e.variables&&0===Object.keys(e.variables).length)return;const r=[];for(var o in e.variables){const i=e.variables[o];if(!i.enum)continue;if(Array.isArray(i.enum)&&0===(null===(t=i.enum)||void 0===t?void 0:t.length)&&r.push(n.empty),!i.default)continue;const a=e.variables[o].default;i.enum&&!i.enum.includes(a)&&r.push(n.invalidDefaultValue)}return r.length?r:void 0}Object.defineProperty(t,"__esModule",{value:!0}),t.NoEmptyEnumServers=void 0,function(e){e.empty="empty",e.invalidDefaultValue="invalidDefaultValue"}(n||(n={})),t.NoEmptyEnumServers=()=>({DefinitionRoot(e,{report:t,location:o}){if(!e.servers||0===e.servers.length)return;const i=[];if(Array.isArray(e.servers))for(const t of e.servers){const e=r(t);e&&i.push(...e)}else{const t=r(e.servers);if(!t)return;i.push(...t)}for(const e of i)e===n.empty&&t({message:"Server variable with `enum` must be a non-empty array.",location:o.child(["servers"]).key()}),e===n.invalidDefaultValue&&t({message:"Server variable define `enum` and `default`. `enum` must include default value",location:o.child(["servers"]).key()})}})},6208:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoUndefinedServerVariable=void 0,t.NoUndefinedServerVariable=()=>({Server(e,{report:t,location:n}){var r;if(!e.url)return;const o=(null===(r=e.url.match(/{[^}]+}/g))||void 0===r?void 0:r.map((e=>e.slice(1,e.length-1))))||[],i=(null==e?void 0:e.variables)&&Object.keys(e.variables)||[];for(const e of o)i.includes(e)||t({message:`The \`${e}\` variable is not defined in the \`variables\` objects.`,location:n.child(["url"])});for(const e of i)o.includes(e)||t({message:`The \`${e}\` variable is not used in the server's \`url\` field.`,location:n.child(["variables",e]).key(),from:n.child("url")})}})},897:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.NoUnusedComponents=void 0,t.NoUnusedComponents=()=>{let e=new Map;function t(t,n){var r;e.set(t.absolutePointer,{used:(null===(r=e.get(t.absolutePointer))||void 0===r?void 0:r.used)||!1,location:t,name:n})}return{ref(t,{type:n,resolve:r,key:o,location:i}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString(),location:i})}},DefinitionRoot:{leave(t,{report:n}){e.forEach((e=>{e.used||n({message:`Component: "${e.name}" is never used.`,location:e.location.key()})}))}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,r.toString())}}}}},6350:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(771);t.RemoveUnusedComponents=()=>{let e=new Map;function t(t,n,r){var o;e.set(t.absolutePointer,{used:(null===(o=e.get(t.absolutePointer))||void 0===o?void 0:o.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:o}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;e.set(n.location.absolutePointer,{used:!0,name:o.toString()})}}},DefinitionRoot:{leave(t,n){const o=n.getVisitorData();o.removedCount=0,e.forEach((e=>{const{used:n,componentType:i,name:a}=e;if(!n&&i){let e=t.components[i];delete e[a],o.removedCount++,r.isEmptyObject(e)&&delete t.components[i]}})),r.isEmptyObject(t.components)&&delete t.components}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"schemas",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,"examples",r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,"requestBodies",r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,"headers",r.toString())}}}}},3736:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RequestMimeType=void 0;const r=n(771);t.RequestMimeType=({allowedValues:e})=>({PathMap:{RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}},Callback:{RequestBody(){},Response:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}}}},WebhooksMap:{Response:{leave(t,n){r.validateMimeTypeOAS3({type:"consumes",value:t},n,e)}}}})},7557:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseContainsProperty=void 0;const r=n(771);t.ResponseContainsProperty=e=>{const t=e.names||{};let n;return{Operation:{Response:{skip:(e,t)=>"204"==`${t}`,enter:(e,t)=>{n=t.key},MediaType:{Schema(e,{report:o,location:i}){var a;if("object"!==e.type)return;const s=t[n]||t[r.getMatchingStatusCodeRange(n)]||t[r.getMatchingStatusCodeRange(n).toLowerCase()]||[];for(const t of s)(null===(a=e.properties)||void 0===a?void 0:a[t])||o({message:`Response object must contain a top-level "${t}" property.`,location:i.child("properties").key()})}}}}}}},503:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ResponseMimeType=void 0;const r=n(771);t.ResponseMimeType=({allowedValues:e})=>({PathMap:{Response:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}},Callback:{Response(){},RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}}}},WebhooksMap:{RequestBody:{leave(t,n){r.validateMimeTypeOAS3({type:"produces",value:t},n,e)}}}})},780:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.validateExample=t.getSuggest=t.validateDefinedAndNonEmpty=t.fieldNonEmpty=t.missingRequiredField=t.matchesJsonSchemaType=t.oasTypeOf=void 0;const r=n(9991),o=n(7468),i=n(7275);function a(e,t){return`${e} object should contain \`${t}\` field.`}function s(e,t){return`${e} object \`${t}\` must be non-empty string.`}t.oasTypeOf=function(e){return Array.isArray(e)?"array":null===e?"null":typeof e},t.matchesJsonSchemaType=function(e,t,n){if(n&&null===e)return null===e;switch(t){case"array":return Array.isArray(e);case"object":return"object"==typeof e&&null!==e&&!Array.isArray(e);case"null":return null===e;case"integer":return Number.isInteger(e);default:return typeof e===t}},t.missingRequiredField=a,t.fieldNonEmpty=s,t.validateDefinedAndNonEmpty=function(e,t,n){"object"==typeof t&&(void 0===t[e]?n.report({message:a(n.type.name,e),location:n.location.child([e]).key()}):t[e]||n.report({message:s(n.type.name,e),location:n.location.child([e]).key()}))},t.getSuggest=function(e,t){if("string"!=typeof e||!t.length)return[];const n=[];for(let o=0;oe.distance-t.distance)),n.map((e=>e.variant))},t.validateExample=function(e,t,n,{resolve:r,location:a,report:s},l){try{const{valid:c,errors:u}=i.validateJsonSchema(e,t,a.child("schema"),n.pointer,r,l);if(!c)for(let e of u)s({message:`Example value must conform to the schema: ${e.message}.`,location:Object.assign(Object.assign({},new o.Location(n.source,e.instancePath)),{reportOnKey:"additionalProperties"===e.keyword}),from:a,suggest:e.suggest})}catch(e){s({message:`Example validation errored: ${e.message}.`,location:a.child("schema"),from:a})}}},5220:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNamedType=t.normalizeTypes=t.mapOf=t.listOf=void 0,t.listOf=function(e){return{name:`${e}List`,properties:{},items:e}},t.mapOf=function(e){return{name:`${e}Map`,properties:{},additionalProperties:()=>e}},t.normalizeTypes=function(e,t={}){const n={};for(const t of Object.keys(e))n[t]=Object.assign(Object.assign({},e[t]),{name:t});for(const e of Object.values(n))r(e);return n;function r(e){if(e.additionalProperties&&(e.additionalProperties=o(e.additionalProperties)),e.items&&(e.items=o(e.items)),e.properties){const n={};for(const[r,i]of Object.entries(e.properties))n[r]=o(i),t.doNotResolveExamples&&i&&i.isExample&&(n[r]=Object.assign(Object.assign({},i),{resolvable:!1}));e.properties=n}}function o(e){if("string"==typeof e){if(!n[e])throw new Error(`Unknown type name found: ${e}`);return n[e]}return"function"==typeof e?(t,n)=>o(e(t,n)):e&&e.name?(r(e=Object.assign({},e)),e):e&&e.directResolveAs?Object.assign(Object.assign({},e),{directResolveAs:o(e.directResolveAs)}):e}},t.isNamedType=function(e){return"string"==typeof(null==e?void 0:e.name)}},388:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas2Types=void 0;const r=n(5220),o=/^[0-9][0-9Xx]{2}$/,i={properties:{swagger:{type:"string"},info:"Info",host:{type:"string"},basePath:{type:"string"},schemes:{type:"array",items:{type:"string"}},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},paths:"PathMap",definitions:"NamedSchemas",parameters:"NamedParameters",responses:"NamedResponses",securityDefinitions:"NamedSecuritySchemes",security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs"},required:["swagger","paths","info"]},a={properties:{$ref:{type:"string"},parameters:r.listOf("Parameter"),get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation"}},s={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},parameters:r.listOf("Parameter"),responses:"ResponsesMap",schemes:{type:"array",items:{type:"string"}},deprecated:{type:"boolean"},security:r.listOf("SecurityRequirement"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}},required:["responses"]},l={properties:{default:"Response"},additionalProperties:(e,t)=>o.test(t)?"Response":void 0},c={properties:{description:{type:"string"},schema:"Schema",headers:r.mapOf("Header"),examples:"Examples"},required:["description"]},u={properties:{format:{type:"string"},title:{type:"string"},description:{type:"string"},default:null,multipleOf:{type:"number"},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"number"},minLength:{type:"number"},pattern:{type:"string"},maxItems:{type:"number"},minItems:{type:"number"},uniqueItems:{type:"boolean"},maxProperties:{type:"number"},minProperties:{type:"number"},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{type:"string",enum:["object","array","string","number","integer","boolean","null"]},items:e=>Array.isArray(e)?r.listOf("Schema"):"Schema",allOf:r.listOf("Schema"),properties:"SchemaProperties",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",discriminator:{type:"string"},readOnly:{type:"boolean"},xml:"Xml",externalDocs:"ExternalDocs",example:{isExample:!0},"x-tags":{type:"array",items:{type:"string"}}}},p={properties:{type:{enum:["basic","apiKey","oauth2"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header"]},flow:{enum:["implicit","password","application","accessCode"]},authorizationUrl:{type:"string"},tokenUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","scopes"];case"application":case"password":return["type","flow","tokenUrl","scopes"];default:return["type","flow","scopes"]}default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"basic":return["type","description"];case"apiKey":return["type","name","in","description"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","description","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","description","scopes"];case"application":case"password":return["type","flow","tokenUrl","description","scopes"];default:return["type","flow","tokenUrl","authorizationUrl","description","scopes"]}default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas2Types={DefinitionRoot:i,Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},Info:{properties:{title:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License",version:{type:"string"}},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},PathMap:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:a,Parameter:{properties:{name:{type:"string"},in:{type:"string",enum:["query","header","path","formData","body"]},description:{type:"string"},required:{type:"boolean"},schema:"Schema",type:{type:"string",enum:["string","number","integer","boolean","array","file"]},format:{type:"string"},allowEmptyValue:{type:"boolean"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&e.in?"body"===e.in?["name","in","schema"]:"array"===e.type?["name","in","type","items"]:["name","in","type"]:["name","in"]},ParameterItems:{properties:{type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&"array"===e.type?["type","items"]:["type"]},Operation:s,Examples:{properties:{},additionalProperties:{isExample:!0}},Header:{properties:{description:{type:"string"},type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required:e=>e&&"array"===e.type?["type","items"]:["type"]},ResponsesMap:l,Response:c,Schema:u,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}}},SchemaProperties:{properties:{},additionalProperties:"Schema"},NamedSchemas:r.mapOf("Schema"),NamedResponses:r.mapOf("Response"),NamedParameters:r.mapOf("Parameter"),NamedSecuritySchemes:r.mapOf("SecurityScheme"),SecurityScheme:p,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}}}},5241:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3Types=void 0;const r=n(5220),o=n(7468),i=/^[0-9][0-9Xx]{2}$/,a={properties:{openapi:null,info:"Info",servers:r.listOf("Server"),security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs",paths:"PathMap",components:"Components","x-webhooks":"WebhooksMap"},required:["openapi","paths","info"]},s={properties:{url:{type:"string"},description:{type:"string"},variables:r.mapOf("ServerVariable")},required:["url"]},l={properties:{$ref:{type:"string"},servers:r.listOf("Server"),parameters:r.listOf("Parameter"),summary:{type:"string"},description:{type:"string"},get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation",trace:"Operation"}},c={properties:{name:{type:"string"},in:{enum:["query","header","path","cookie"]},description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),content:"MediaTypeMap"},required:["name","in"],requiredOneOf:["schema","content"]},u={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:r.listOf("Parameter"),security:r.listOf("SecurityRequirement"),servers:r.listOf("Server"),requestBody:"RequestBody",responses:"ResponsesMap",deprecated:{type:"boolean"},callbacks:r.mapOf("Callback"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}},required:["responses"]},p={properties:{schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),encoding:r.mapOf("Encoding")}},d={properties:{contentType:{type:"string"},headers:r.mapOf("Header"),style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"}}},f={properties:{description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:r.mapOf("Example"),content:"MediaTypeMap"}},h={properties:{default:"Response"},additionalProperties:(e,t)=>i.test(t)?"Response":void 0},m={properties:{description:{type:"string"},headers:r.mapOf("Header"),content:"MediaTypeMap",links:r.mapOf("Link")},required:["description"]},g={properties:{externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{enum:["object","array","string","number","integer","boolean","null"]},allOf:r.listOf("Schema"),anyOf:r.listOf("Schema"),oneOf:r.listOf("Schema"),not:"Schema",properties:"SchemaProperties",items:e=>Array.isArray(e)?r.listOf("Schema"):"Schema",additionalItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},default:null,nullable:{type:"boolean"},readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",example:{isExample:!0},deprecated:{type:"boolean"},"x-tags":{type:"array",items:{type:"string"}}}},y={properties:{},additionalProperties:e=>o.isMappingRef(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},v={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3Types={DefinitionRoot:a,Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},Server:s,ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:null},required:["default"]},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},PathMap:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:l,Parameter:c,Operation:u,Callback:r.mapOf("PathItem"),RequestBody:{properties:{description:{type:"string"},required:{type:"boolean"},content:"MediaTypeMap"},required:["content"]},MediaTypeMap:{properties:{},additionalProperties:"MediaType"},MediaType:p,Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}}},Encoding:d,Header:f,ResponsesMap:h,Response:m,Link:{properties:{operationRef:{type:"string"},operationId:{type:"string"},parameters:null,requestBody:null,description:{type:"string"},server:"Server"}},Schema:g,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}}},SchemaProperties:{properties:{},additionalProperties:"Schema"},DiscriminatorMapping:y,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"]},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks"}},NamedSchemas:r.mapOf("Schema"),NamedResponses:r.mapOf("Response"),NamedParameters:r.mapOf("Parameter"),NamedExamples:r.mapOf("Example"),NamedRequestBodies:r.mapOf("RequestBody"),NamedHeaders:r.mapOf("Header"),NamedSecuritySchemes:r.mapOf("SecurityScheme"),NamedLinks:r.mapOf("Link"),NamedCallbacks:r.mapOf("Callback"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"]},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["authorizationUrl","tokenUrl","scopes"]},SecuritySchemeFlows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"}},SecurityScheme:v,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},WebhooksMap:{properties:{},additionalProperties:()=>"PathItem"}}},2608:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3_1Types=void 0;const r=n(5220),o=n(5241),i={properties:{openapi:null,info:"Info",servers:r.listOf("Server"),security:r.listOf("SecurityRequirement"),tags:r.listOf("Tag"),externalDocs:"ExternalDocs",paths:"PathMap",webhooks:"WebhooksMap",components:"Components",jsonSchemaDialect:{type:"string"}},required:["openapi","info"],requiredOneOf:["paths","components","webhooks"]},a={properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:r.listOf("Parameter"),security:r.listOf("SecurityRequirement"),servers:r.listOf("Server"),requestBody:"RequestBody",responses:"ResponsesMap",deprecated:{type:"boolean"},callbacks:r.mapOf("Callback"),"x-codeSamples":r.listOf("XCodeSample"),"x-code-samples":r.listOf("XCodeSample"),"x-hideTryItPanel":{type:"boolean"}}},s={properties:{$id:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",myArbitraryKeyword:{type:"boolean"},title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:r.listOf("Schema"),anyOf:r.listOf("Schema"),oneOf:r.listOf("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:r.listOf("Schema"),prefixItems:r.listOf("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},"x-tags":{type:"array",items:{type:"string"}}}},l={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect","mutualTLS"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":switch(null==e?void 0:e.flows){case"implicit":return["type","flows","authorizationUrl","refreshUrl","description","scopes"];case"password":case"clientCredentials":return["type","flows","tokenUrl","refreshUrl","description","scopes"];default:return["type","flows","authorizationUrl","refreshUrl","tokenUrl","description","scopes"]}case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3_1Types=Object.assign(Object.assign({},o.Oas3Types),{Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},summary:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},DefinitionRoot:i,Schema:s,License:{properties:{name:{type:"string"},url:{type:"string"},identifier:{type:"string"}},required:["name"]},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks",pathItems:"NamedPathItems"}},NamedPathItems:r.mapOf("PathItem"),SecurityScheme:l,Operation:a})},771:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isCustomRuleId=t.getMatchingStatusCodeRange=t.assignExisting=t.isNotString=t.isString=t.isNotEmptyObject=t.slash=t.isPathParameter=t.readFileAsStringSync=t.isSingular=t.validateMimeTypeOAS3=t.validateMimeType=t.splitCamelCaseIntoWords=t.omitObjectProps=t.pickObjectProps=t.readFileFromUrl=t.isEmptyArray=t.isEmptyObject=t.isPlainObject=t.notUndefined=t.loadYaml=t.popStack=t.pushStack=t.stringifyYaml=t.parseYaml=void 0;const o=n(3197),i=n(4099),a=n(8150),s=n(3450),l=n(5273),c=n(8698);var u=n(5273);function p(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function d(e,t){return t.match(/^https?:\/\//)||(e=e.replace(/^https?:\/\//,"")),i(e,t)}function f(e){return"string"==typeof e}Object.defineProperty(t,"parseYaml",{enumerable:!0,get:function(){return u.parseYaml}}),Object.defineProperty(t,"stringifyYaml",{enumerable:!0,get:function(){return u.stringifyYaml}}),t.pushStack=function(e,t){return{prev:e,value:t}},t.popStack=function(e){var t;return null!==(t=null==e?void 0:e.prev)&&void 0!==t?t:null},t.loadYaml=function(e){return r(this,void 0,void 0,(function*(){const t=yield o.promises.readFile(e,"utf-8");return l.parseYaml(t)}))},t.notUndefined=function(e){return void 0!==e},t.isPlainObject=p,t.isEmptyObject=function(e){return p(e)&&0===Object.keys(e).length},t.isEmptyArray=function(e){return Array.isArray(e)&&0===e.length},t.readFileFromUrl=function(e,t){return r(this,void 0,void 0,(function*(){const n={};for(const r of t.headers)d(e,r.matches)&&(n[r.name]=void 0!==r.envVariable?c.env[r.envVariable]||"":r.value);const r=yield(t.customFetch||a.default)(e,{headers:n});if(!r.ok)throw new Error(`Failed to load ${e}: ${r.status} ${r.statusText}`);return{body:yield r.text(),mimeType:r.headers.get("content-type")}}))},t.pickObjectProps=function(e,t){return Object.fromEntries(t.filter((t=>t in e)).map((t=>[t,e[t]])))},t.omitObjectProps=function(e,t){return Object.fromEntries(Object.entries(e).filter((([e])=>!t.includes(e))))},t.splitCamelCaseIntoWords=function(e){const t=e.split(/(?:[-._])|([A-Z][a-z]+)/).filter(Boolean).map((e=>e.toLocaleLowerCase())),n=e.split(/([A-Z]{2,})/).filter((e=>e&&e===e.toUpperCase())).map((e=>e.toLocaleLowerCase()));return new Set([...t,...n])},t.validateMimeType=function({type:e,value:t},{report:n,location:r},o){if(!o)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t[e])for(const i of t[e])o.includes(i)||n({message:`Mime type "${i}" is not allowed`,location:r.child(t[e].indexOf(i)).key()})},t.validateMimeTypeOAS3=function({type:e,value:t},{report:n,location:r},o){if(!o)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t.content)for(const e of Object.keys(t.content))o.includes(e)||n({message:`Mime type "${e}" is not allowed`,location:r.child("content").child(e).key()})},t.isSingular=function(e){return s.isSingular(e)},t.readFileAsStringSync=function(e){return o.readFileSync(e,"utf-8")},t.isPathParameter=function(e){return e.startsWith("{")&&e.endsWith("}")},t.slash=function(e){return/^\\\\\?\\/.test(e)?e:e.replace(/\\/g,"/")},t.isNotEmptyObject=function(e){return!!e&&Object.keys(e).length>0},t.isString=f,t.isNotString=function(e){return!f(e)},t.assignExisting=function(e,t){for(let n of Object.keys(t))e.hasOwnProperty(n)&&(e[n]=t[n])},t.getMatchingStatusCodeRange=e=>`${e}`.replace(/^(\d)\d\d$/,((e,t)=>`${t}XX`)),t.isCustomRuleId=function(e){return e.includes("/")}},8065:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizeVisitors=void 0,t.normalizeVisitors=function(e,t){const n={any:{enter:[],leave:[]}};for(const e of Object.keys(t))n[e]={enter:[],leave:[]};n.ref={enter:[],leave:[]};for(const{ruleId:t,severity:n,visitor:r}of e)o({ruleId:t,severity:n},r,null);for(const e of Object.keys(n))n[e].enter.sort(((e,t)=>t.depth-e.depth)),n[e].leave.sort(((e,t)=>e.depth-t.depth));return n;function r(e,t,o,i,a=[]){if(a.includes(t))return;a=[...a,t];const s=new Set;for(let n of Object.values(t.properties))n!==o?"object"==typeof n&&null!==n&&n.name&&s.add(n):l(e,a);t.additionalProperties&&"function"!=typeof t.additionalProperties&&(t.additionalProperties===o?l(e,a):void 0!==t.additionalProperties.name&&s.add(t.additionalProperties)),t.items&&(t.items===o?l(e,a):void 0!==t.items.name&&s.add(t.items));for(let t of Array.from(s.values()))r(e,t,o,i,a);function l(e,t){for(const r of t.slice(1))n[r.name]=n[r.name]||{enter:[],leave:[]},n[r.name].enter.push(Object.assign(Object.assign({},e),{visit:()=>{},depth:0,context:{isSkippedLevel:!0,seen:new Set,parent:i}}))}}function o(e,i,a,s=0){const l=Object.keys(t);if(0===s)l.push("any"),l.push("ref");else{if(i.any)throw new Error("any() is allowed only on top level");if(i.ref)throw new Error("ref() is allowed only on top level")}for(const c of l){const l=i[c],u=n[c];if(!l)continue;let p,d,f;const h="object"==typeof l;if("ref"===c&&h&&l.skip)throw new Error("ref() visitor does not support skip");"function"==typeof l?p=l:h&&(p=l.enter,d=l.leave,f=l.skip);const m={activatedOn:null,type:t[c],parent:a,isSkippedLevel:!1};if("object"==typeof l&&o(e,l,m,s+1),a&&r(e,a.type,t[c],a),p||h){if(p&&"function"!=typeof p)throw new Error("DEV: should be function");u.enter.push(Object.assign(Object.assign({},e),{visit:p||(()=>{}),skip:f,depth:s,context:m}))}if(d){if("function"!=typeof d)throw new Error("DEV: should be function");u.leave.push(Object.assign(Object.assign({},e),{visit:d,depth:s,context:m}))}}}}},9443:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.walkDocument=void 0;const r=n(7468),o=n(4182),i=n(771),a=n(5220);function s(e){var t,n;const r={};for(;e.parent;)(null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.location)&&(r[e.parent.type.name]=null===(n=e.parent.activatedOn)||void 0===n?void 0:n.value.location),e=e.parent;return r}t.walkDocument=function(e){const{document:t,rootType:n,normalizedVisitors:l,resolvedRefMap:c,ctx:u}=e,p={},d=new Set;!function e(t,n,f,h,m){var g,y,v,b,w,x,k,_,O,S,E;const P=(e,t=$.source.absoluteRef)=>{if(!r.isRef(e))return{location:f,node:e};const n=o.makeRefId(t,e.$ref),i=c.get(n);if(!i)return{location:void 0,node:void 0};const{resolved:a,node:s,document:l,nodePointer:u,error:p}=i;return{location:a?new r.Location(l.source,u):p instanceof o.YamlParseError?new r.Location(p.source,""):void 0,node:s,error:p}},A=f;let $=f;const{node:C,location:R,error:j}=P(t),T=new Set;if(r.isRef(t)){const e=l.ref.enter;for(const{visit:r,ruleId:o,severity:i,context:a}of e)if(!d.has(t)){T.add(a);r(t,{report:N.bind(void 0,o,i),resolve:P,rawNode:t,rawLocation:A,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:D.bind(void 0,o)},{node:C,location:R,error:j}),(null==R?void 0:R.source.absoluteRef)&&u.refTypes&&u.refTypes.set(null==R?void 0:R.source.absoluteRef,n)}}if(void 0!==C&&R&&"scalar"!==n.name){$=R;const o=null===(y=null===(g=p[n.name])||void 0===g?void 0:g.has)||void 0===y?void 0:y.call(g,C);let s=!1;const c=l.any.enter.concat((null===(v=l[n.name])||void 0===v?void 0:v.enter)||[]),u=[];for(const{context:e,visit:r,skip:a,ruleId:l,severity:p}of c)if(e.isSkippedLevel)!e.parent.activatedOn||e.parent.activatedOn.value.nextLevelTypeActivated||e.seen.has(t)||(e.seen.add(t),s=!0,u.push(e));else if(e.parent&&e.parent.activatedOn&&(null===(b=e.activatedOn)||void 0===b?void 0:b.value.withParentNode)!==e.parent.activatedOn.value.node&&(null===(w=e.parent.activatedOn.value.nextLevelTypeActivated)||void 0===w?void 0:w.value)!==n||!e.parent&&!o){u.push(e);const o={node:C,location:R,nextLevelTypeActivated:null,withParentNode:null===(k=null===(x=e.parent)||void 0===x?void 0:x.activatedOn)||void 0===k?void 0:k.value.node,skipped:null!==(S=(null===(O=null===(_=e.parent)||void 0===_?void 0:_.activatedOn)||void 0===O?void 0:O.value.skipped)||(null==a?void 0:a(C,m)))&&void 0!==S&&S};e.activatedOn=i.pushStack(e.activatedOn,o);let c=e.parent;for(;c;)c.activatedOn.value.nextLevelTypeActivated=i.pushStack(c.activatedOn.value.nextLevelTypeActivated,n),c=c.parent;if(!o.skipped){s=!0,T.add(e);const{ignoreNextVisitorsOnNode:n}=I(r,C,t,e,l,p);if(n)break}}if(s||!o)if(p[n.name]=p[n.name]||new Set,p[n.name].add(C),Array.isArray(C)){const t=n.items;if(void 0!==t)for(let n=0;n!o.includes(e)))),r.isRef(t)&&o.push(...Object.keys(t).filter((e=>"$ref"!==e&&!o.includes(e))));for(const i of o){let o=C[i],s=R;void 0===o&&(o=t[i],s=f);let l=n.properties[i];void 0===l&&(l=n.additionalProperties),"function"==typeof l&&(l=l(o,i)),!a.isNamedType(l)&&(null==l?void 0:l.directResolveAs)&&(l=l.directResolveAs,o={$ref:o}),l&&void 0===l.name&&!1!==l.resolvable&&(l={name:"scalar",properties:{}}),a.isNamedType(l)&&("scalar"!==l.name||r.isRef(o))&&e(o,l,s.child([i]),C,i)}}const d=l.any.leave,h=((null===(E=l[n.name])||void 0===E?void 0:E.leave)||[]).concat(d);for(const e of u.reverse())if(e.isSkippedLevel)e.seen.delete(C);else if(e.activatedOn=i.popStack(e.activatedOn),e.parent){let t=e.parent;for(;t;)t.activatedOn.value.nextLevelTypeActivated=i.popStack(t.activatedOn.value.nextLevelTypeActivated),t=t.parent}for(const{context:e,visit:n,ruleId:r,severity:o}of h)!e.isSkippedLevel&&T.has(e)&&I(n,C,t,e,r,o)}if($=f,r.isRef(t)){const e=l.ref.leave;for(const{visit:r,ruleId:o,severity:i,context:a}of e)if(T.has(a)){r(t,{report:N.bind(void 0,o,i),resolve:P,rawNode:t,rawLocation:A,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:D.bind(void 0,o)},{node:C,location:R,error:j})}}function I(e,t,r,o,i,a){const l=N.bind(void 0,i,a);let c=!1;return e(t,{report:l,resolve:P,rawNode:r,location:$,rawLocation:A,type:n,parent:h,key:m,parentLocations:s(o),oasVersion:u.oasVersion,ignoreNextVisitorsOnNode:()=>{c=!0},getVisitorData:D.bind(void 0,i)},function(e){var t;const n={};for(;e.parent;)n[e.parent.type.name]=null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.node,e=e.parent;return n}(o),o),{ignoreNextVisitorsOnNode:c}}function N(e,t,n){const r=n.location?Array.isArray(n.location)?n.location:[n.location]:[Object.assign(Object.assign({},$),{reportOnKey:!1})];u.problems.push(Object.assign(Object.assign({ruleId:n.ruleId||e,severity:n.forceSeverity||t},n),{suggest:n.suggest||[],location:r.map((e=>Object.assign(Object.assign(Object.assign({},$),{reportOnKey:!1}),e)))}))}function D(e){return u.visitorsData[e]=u.visitorsData[e]||{},u.visitorsData[e]}}(t.parsed,n,new r.Location(t.source,"#/"),void 0,"")}},5019:function(e,t,n){var r=n(5623);e.exports=function(e){return e?("{}"===e.substr(0,2)&&(e="\\{\\}"+e.substr(2)),g(function(e){return e.split("\\\\").join(o).split("\\{").join(i).split("\\}").join(a).split("\\,").join(s).split("\\.").join(l)}(e),!0).map(u)):[]};var o="\0SLASH"+Math.random()+"\0",i="\0OPEN"+Math.random()+"\0",a="\0CLOSE"+Math.random()+"\0",s="\0COMMA"+Math.random()+"\0",l="\0PERIOD"+Math.random()+"\0";function c(e){return parseInt(e,10)==e?parseInt(e,10):e.charCodeAt(0)}function u(e){return e.split(o).join("\\").split(i).join("{").split(a).join("}").split(s).join(",").split(l).join(".")}function p(e){if(!e)return[""];var t=[],n=r("{","}",e);if(!n)return e.split(",");var o=n.pre,i=n.body,a=n.post,s=o.split(",");s[s.length-1]+="{"+i+"}";var l=p(a);return a.length&&(s[s.length-1]+=l.shift(),s.push.apply(s,l)),t.push.apply(t,s),t}function d(e){return"{"+e+"}"}function f(e){return/^-?0\d/.test(e)}function h(e,t){return e<=t}function m(e,t){return e>=t}function g(e,t){var n=[],o=r("{","}",e);if(!o)return[e];var i=o.pre,s=o.post.length?g(o.post,!1):[""];if(/\$$/.test(o.pre))for(var l=0;l=0;if(!x&&!k)return o.post.match(/,.*\}/)?g(e=o.pre+"{"+o.body+a+o.post):[e];if(x)y=o.body.split(/\.\./);else if(1===(y=p(o.body)).length&&1===(y=g(y[0],!1).map(d)).length)return s.map((function(e){return o.pre+y[0]+e}));if(x){var _=c(y[0]),O=c(y[1]),S=Math.max(y[0].length,y[1].length),E=3==y.length?Math.abs(c(y[2])):1,P=h;O<_&&(E*=-1,P=m);var A=y.some(f);v=[];for(var $=_;P($,O);$+=E){var C;if(w)"\\"===(C=String.fromCharCode($))&&(C="");else if(C=String($),A){var R=S-C.length;if(R>0){var j=new Array(R+1).join("0");C=$<0?"-"+j+C.slice(1):j+C}}v.push(C)}}else{v=[];for(var T=0;T(g(t),!(!n.nocomment&&"#"===t.charAt(0))&&new v(t,n).match(e));e.exports=r;const o=n(5751);r.sep=o.sep;const i=Symbol("globstar **");r.GLOBSTAR=i;const a=n(5019),s={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},l="[^/]",c="[^/]*?",u=e=>e.split("").reduce(((e,t)=>(e[t]=!0,e)),{}),p=u("().*{}+?[]^$\\!"),d=u("[.("),f=/\/+/;r.filter=(e,t={})=>(n,o,i)=>r(n,e,t);const h=(e,t={})=>{const n={};return Object.keys(e).forEach((t=>n[t]=e[t])),Object.keys(t).forEach((e=>n[e]=t[e])),n};r.defaults=e=>{if(!e||"object"!=typeof e||!Object.keys(e).length)return r;const t=r,n=(n,r,o)=>t(n,r,h(e,o));return(n.Minimatch=class extends t.Minimatch{constructor(t,n){super(t,h(e,n))}}).defaults=n=>t.defaults(h(e,n)).Minimatch,n.filter=(n,r)=>t.filter(n,h(e,r)),n.defaults=n=>t.defaults(h(e,n)),n.makeRe=(n,r)=>t.makeRe(n,h(e,r)),n.braceExpand=(n,r)=>t.braceExpand(n,h(e,r)),n.match=(n,r,o)=>t.match(n,r,h(e,o)),n},r.braceExpand=(e,t)=>m(e,t);const m=(e,t={})=>(g(e),t.nobrace||!/\{(?:(?!\{).)*\}/.test(e)?[e]:a(e)),g=e=>{if("string"!=typeof e)throw new TypeError("invalid pattern");if(e.length>65536)throw new TypeError("pattern is too long")},y=Symbol("subparse");r.makeRe=(e,t)=>new v(e,t||{}).makeRe(),r.match=(e,t,n={})=>{const r=new v(t,n);return e=e.filter((e=>r.match(e))),r.options.nonull&&!e.length&&e.push(t),e};class v{constructor(e,t){g(e),t||(t={}),this.options=t,this.set=[],this.pattern=e,this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.partial=!!t.partial,this.make()}debug(){}make(){const e=this.pattern,t=this.options;if(!t.nocomment&&"#"===e.charAt(0))return void(this.comment=!0);if(!e)return void(this.empty=!0);this.parseNegate();let n=this.globSet=this.braceExpand();t.debug&&(this.debug=(...e)=>console.error(...e)),this.debug(this.pattern,n),n=this.globParts=n.map((e=>e.split(f))),this.debug(this.pattern,n),n=n.map(((e,t,n)=>e.map(this.parse,this))),this.debug(this.pattern,n),n=n.filter((e=>-1===e.indexOf(!1))),this.debug(this.pattern,n),this.set=n}parseNegate(){if(this.options.nonegate)return;const e=this.pattern;let t=!1,n=0;for(let r=0;r>> no match, partial?",e,d,t,f),d!==s))}if("string"==typeof u?(c=p===u,this.debug("string match",u,p,c)):(c=p.match(u),this.debug("pattern match",u,p,c)),!c)return!1}if(o===s&&a===l)return!0;if(o===s)return n;if(a===l)return o===s-1&&""===e[o];throw new Error("wtf?")}braceExpand(){return m(this.pattern,this.options)}parse(e,t){g(e);const n=this.options;if("**"===e){if(!n.noglobstar)return i;e="*"}if(""===e)return"";let r="",o=!!n.nocase,a=!1;const u=[],f=[];let h,m,v,b,w=!1,x=-1,k=-1;const _="."===e.charAt(0)?"":n.dot?"(?!(?:^|\\/)\\.{1,2}(?:$|\\/))":"(?!\\.)",O=()=>{if(h){switch(h){case"*":r+=c,o=!0;break;case"?":r+=l,o=!0;break;default:r+="\\"+h}this.debug("clearStateChar %j %j",h,r),h=!1}};for(let t,i=0;i(n||(n="\\"),t+t+n+"|"))),this.debug("tail=%j\n %s",e,e,v,r);const t="*"===v.type?c:"?"===v.type?l:"\\"+v.type;o=!0,r=r.slice(0,v.reStart)+t+"\\("+e}O(),a&&(r+="\\\\");const S=d[r.charAt(0)];for(let e=f.length-1;e>-1;e--){const n=f[e],o=r.slice(0,n.reStart),i=r.slice(n.reStart,n.reEnd-8);let a=r.slice(n.reEnd);const s=r.slice(n.reEnd-8,n.reEnd)+a,l=o.split("(").length-1;let c=a;for(let e=0;e(e=e.map((e=>"string"==typeof e?e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"):e===i?i:e._src)).reduce(((e,t)=>(e[e.length-1]===i&&t===i||e.push(t),e)),[]),e.forEach(((t,r)=>{t===i&&e[r-1]!==i&&(0===r?e.length>1?e[r+1]="(?:\\/|"+n+"\\/)?"+e[r+1]:e[r]=n:r===e.length-1?e[r-1]+="(?:\\/|"+n+")?":(e[r-1]+="(?:\\/|\\/"+n+"\\/)"+e[r+1],e[r+1]=i))})),e.filter((e=>e!==i)).join("/")))).join("|");o="^(?:"+o+")$",this.negate&&(o="^(?!"+o+").*$");try{this.regexp=new RegExp(o,r)}catch(e){this.regexp=!1}return this.regexp}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return""===e;if("/"===e&&t)return!0;const n=this.options;"/"!==o.sep&&(e=e.split(o.sep).join("/")),e=e.split(f),this.debug(this.pattern,"split",e);const r=this.set;let i;this.debug(this.pattern,"set",r);for(let t=e.length-1;t>=0&&(i=e[t],!i);t--);for(let o=0;o=0&&c>0){if(e===t)return[l,c];for(r=[],i=n.length;u>=0&&!s;)u==l?(r.push(u),l=n.indexOf(e,u+1)):1==r.length?s=[r.pop(),c]:((o=r.pop())=0?l:c;r.length&&(s=[i,a])}return s}e.exports=t,t.range=r},4480:function(e,t,n){"use strict";var r=n.g.process&&process.nextTick||n.g.setImmediate||function(e){setTimeout(e,0)};e.exports=function(e,t){return e?void t.then((function(t){r((function(){e(null,t)}))}),(function(t){r((function(){e(t)}))})):t}},4184:function(e,t){var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;tu;)if((s=l[u++])!=s)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},2092:function(e,t,n){var r=n(9974),o=n(8361),i=n(7908),a=n(7466),s=n(5417),l=[].push,c=function(e){var t=1==e,n=2==e,c=3==e,u=4==e,p=6==e,d=7==e,f=5==e||p;return function(h,m,g,y){for(var v,b,w=i(h),x=o(w),k=r(m,g,3),_=a(x.length),O=0,S=y||s,E=t?S(h,_):n||d?S(h,0):void 0;_>O;O++)if((f||O in x)&&(b=k(v=x[O],O,w),e))if(t)E[O]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return O;case 2:l.call(E,v)}else switch(e){case 4:return!1;case 7:l.call(E,v)}return p?-1:c||u?u:E}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},1194:function(e,t,n){var r=n(7293),o=n(5112),i=n(7392),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},5417:function(e,t,n){var r=n(111),o=n(3157),i=n(5112)("species");e.exports=function(e,t){var n;return o(e)&&("function"!=typeof(n=e.constructor)||n!==Array&&!o(n.prototype)?r(n)&&null===(n=n[i])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===t?0:t)}},4326:function(e){var t={}.toString;e.exports=function(e){return t.call(e).slice(8,-1)}},648:function(e,t,n){var r=n(1694),o=n(4326),i=n(5112)("toStringTag"),a="Arguments"==o(function(){return arguments}());e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),i))?n:a?o(t):"Object"==(r=o(t))&&"function"==typeof t.callee?"Arguments":r}},9920:function(e,t,n){var r=n(6656),o=n(3887),i=n(1236),a=n(3070);e.exports=function(e,t){for(var n=o(t),s=a.f,l=i.f,c=0;c=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=r[1]),e.exports=o&&+o},748:function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:function(e,t,n){var r=n(7854),o=n(1236).f,i=n(8880),a=n(1320),s=n(3505),l=n(9920),c=n(4705);e.exports=function(e,t){var n,u,p,d,f,h=e.target,m=e.global,g=e.stat;if(n=m?r:g?r[h]||s(h,{}):(r[h]||{}).prototype)for(u in t){if(d=t[u],p=e.noTargetGet?(f=o(n,u))&&f.value:n[u],!c(m?u:h+(g?".":"#")+u,e.forced)&&void 0!==p){if(typeof d==typeof p)continue;l(d,p)}(e.sham||p&&p.sham)&&i(d,"sham",!0),a(n,u,d,e)}}},7293:function(e){e.exports=function(e){try{return!!e()}catch(e){return!0}}},9974:function(e,t,n){var r=n(3099);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,o){return e.call(t,n,r,o)}}return function(){return e.apply(t,arguments)}}},5005:function(e,t,n){var r=n(857),o=n(7854),i=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},7854:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},6656:function(e,t,n){var r=n(7908),o={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return o.call(r(e),t)}},3501:function(e){e.exports={}},490:function(e,t,n){var r=n(5005);e.exports=r("document","documentElement")},4664:function(e,t,n){var r=n(9781),o=n(7293),i=n(317);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:function(e,t,n){var r=n(7293),o=n(4326),i="".split;e.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==o(e)?i.call(e,""):Object(e)}:Object},2788:function(e,t,n){var r=n(5465),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return o.call(e)}),e.exports=r.inspectSource},9909:function(e,t,n){var r,o,i,a=n(8536),s=n(7854),l=n(111),c=n(8880),u=n(6656),p=n(5465),d=n(6200),f=n(3501),h="Object already initialized",m=s.WeakMap;if(a||p.state){var g=p.state||(p.state=new m),y=g.get,v=g.has,b=g.set;r=function(e,t){if(v.call(g,e))throw new TypeError(h);return t.facade=e,b.call(g,e,t),t},o=function(e){return y.call(g,e)||{}},i=function(e){return v.call(g,e)}}else{var w=d("state");f[w]=!0,r=function(e,t){if(u(e,w))throw new TypeError(h);return t.facade=e,c(e,w,t),t},o=function(e){return u(e,w)?e[w]:{}},i=function(e){return u(e,w)}}e.exports={set:r,get:o,has:i,enforce:function(e){return i(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},3157:function(e,t,n){var r=n(4326);e.exports=Array.isArray||function(e){return"Array"==r(e)}},4705:function(e,t,n){var r=n(7293),o=/#|\.prototype\./,i=function(e,t){var n=s[a(e)];return n==c||n!=l&&("function"==typeof t?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},s=i.data={},l=i.NATIVE="N",c=i.POLYFILL="P";e.exports=i},111:function(e){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},1913:function(e){e.exports=!1},133:function(e,t,n){var r=n(7392),o=n(7293);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},8536:function(e,t,n){var r=n(7854),o=n(2788),i=r.WeakMap;e.exports="function"==typeof i&&/native code/.test(o(i))},30:function(e,t,n){var r,o=n(9670),i=n(6048),a=n(748),s=n(3501),l=n(490),c=n(317),u=n(6200)("IE_PROTO"),p=function(){},d=function(e){return" + + diff --git a/backend/static/drf-yasg/swagger-ui-dist/oauth2-redirect.html.gz b/backend/static/drf-yasg/swagger-ui-dist/oauth2-redirect.html.gz new file mode 100644 index 0000000..fc6d666 Binary files /dev/null and b/backend/static/drf-yasg/swagger-ui-dist/oauth2-redirect.html.gz differ diff --git a/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js b/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js new file mode 100644 index 0000000..8ea0ea3 --- /dev/null +++ b/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js @@ -0,0 +1,20 @@ +window.onload = function() { + // + + // the following lines will be replaced by docker/configurator, when it runs in a docker-container + window.ui = SwaggerUIBundle({ + url: "https://petstore.swagger.io/v2/swagger.json", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }); + + // +}; diff --git a/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js.gz b/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js.gz new file mode 100644 index 0000000..415aa9f Binary files /dev/null and b/backend/static/drf-yasg/swagger-ui-dist/swagger-initializer.js.gz differ diff --git a/backend/static/drf-yasg/swagger-ui-dist/swagger-ui-bundle.js b/backend/static/drf-yasg/swagger-ui-dist/swagger-ui-bundle.js new file mode 100644 index 0000000..44a1009 --- /dev/null +++ b/backend/static/drf-yasg/swagger-ui-dist/swagger-ui-bundle.js @@ -0,0 +1,3 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,a=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,s=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(a,"").trim();if(!l)return"about:blank";if(function(e){return s.indexOf(e[0])>-1}(l))return l;var u=l.match(i);if(!u)return l;var c=u[0];return n.test(c)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>j});var r=n(23101),o=n.n(r),a=n(61125),i=n.n(a),s=n(11882),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294),f=n(43393);function h(e){return h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},h(e)}function d(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,f.is)(t[n],e[n]);if(Array.isArray(n))return(0,f.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return!_(this.updateOnProps,this.props,e,"updateOnProps")||!_(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var A=n(23930),C=n.n(A),k=n(45697),O=n.n(k);class j extends S{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?e.replace(/^.*#\/definitions\//,""):-1!==l()(e).call(e,"#/components/schemas/")?e.replace(/^.*#\/components\/schemas\//,""):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:a,required:i,name:s,isRef:l,specPath:u,displayName:c,includeReadOnly:f,includeWriteOnly:h}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=a&&a.get("$$ref");if(!s&&v&&(s=this.getModelName(v)),!a&&v&&(a=this.getRefSchema(s)),!a)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},c||s),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&a.get("deprecated");switch(l=void 0!==l?l:!!v,y=a&&a.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:u,getConfigs:t,schema:a,name:s,deprecated:b,isRef:l,includeReadOnly:f,includeWriteOnly:h}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:a,name:s,deprecated:b,required:i,includeReadOnly:f,includeWriteOnly:h}));default:return p.createElement(g,o()({},this.props,{getComponent:e,getConfigs:t,schema:a,name:s,deprecated:b,required:i}))}}}i()(j,"propTypes",{schema:c()(C()).isRequired,getComponent:O().func.isRequired,getConfigs:O().func.isRequired,specSelectors:O().object.isRequired,name:O().string,displayName:O().string,isRef:O().bool,required:O().bool,expandDepth:O().number,depth:O().number,specPath:C().list.isRequired,includeReadOnly:O().bool,includeWriteOnly:O().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>f});var r=n(61125),o=n.n(r),a=n(28222),i=n.n(a),s=n(67294),l=n(84564),u=n.n(l),c=n(90242),p=n(27504);class f extends s.Component{constructor(e,t){super(e,t),o()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(u())(e.url(),p.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:r}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===r?"https://validator.swagger.io/validator":r}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,c.Nm)(this.state.validatorUrl);return"object"==typeof t&&i()(t).length?null:this.state.url&&(0,c.hW)(this.state.validatorUrl)&&(0,c.hW)(this.state.url)?s.createElement("span",{className:"float-right"},s.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},s.createElement(h,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class h extends s.Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?s.createElement("img",{alt:"Error"}):this.state.loaded?s.createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},94994:(e,t,n)=>{"use strict";n.d(t,{Z:()=>ye,s:()=>ve});var r=n(67294),o=n(89927);function a(e,t){if(Array.prototype.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n=0;n--)!0===t(e[n])&&e.splice(n,1)}function s(e){throw new Error("Unhandled case for value: '".concat(e,"'"))}var l=function(){function e(e){void 0===e&&(e={}),this.tagName="",this.attrs={},this.innerHTML="",this.whitespaceRegex=/\s+/,this.tagName=e.tagName||"",this.attrs=e.attrs||{},this.innerHTML=e.innerHtml||e.innerHTML||""}return e.prototype.setTagName=function(e){return this.tagName=e,this},e.prototype.getTagName=function(){return this.tagName||""},e.prototype.setAttr=function(e,t){return this.getAttrs()[e]=t,this},e.prototype.getAttr=function(e){return this.getAttrs()[e]},e.prototype.setAttrs=function(e){return Object.assign(this.getAttrs(),e),this},e.prototype.getAttrs=function(){return this.attrs||(this.attrs={})},e.prototype.setClass=function(e){return this.setAttr("class",e)},e.prototype.addClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);t=i.shift();)-1===a(o,t)&&o.push(t);return this.getAttrs().class=o.join(" "),this},e.prototype.removeClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);o.length&&(t=i.shift());){var s=a(o,t);-1!==s&&o.splice(s,1)}return this.getAttrs().class=o.join(" "),this},e.prototype.getClass=function(){return this.getAttrs().class||""},e.prototype.hasClass=function(e){return-1!==(" "+this.getClass()+" ").indexOf(" "+e+" ")},e.prototype.setInnerHTML=function(e){return this.innerHTML=e,this},e.prototype.setInnerHtml=function(e){return this.setInnerHTML(e)},e.prototype.getInnerHTML=function(){return this.innerHTML||""},e.prototype.getInnerHtml=function(){return this.getInnerHTML()},e.prototype.toAnchorString=function(){var e=this.getTagName(),t=this.buildAttrsStr();return["<",e,t=t?" "+t:"",">",this.getInnerHtml(),""].join("")},e.prototype.buildAttrsStr=function(){if(!this.attrs)return"";var e=this.getAttrs(),t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n+'="'+e[n]+'"');return t.join(" ")},e}();var u=function(){function e(e){void 0===e&&(e={}),this.newWindow=!1,this.truncate={},this.className="",this.newWindow=e.newWindow||!1,this.truncate=e.truncate||{},this.className=e.className||""}return e.prototype.build=function(e){return new l({tagName:"a",attrs:this.createAttrs(e),innerHtml:this.processAnchorText(e.getAnchorText())})},e.prototype.createAttrs=function(e){var t={href:e.getAnchorHref()},n=this.createCssClass(e);return n&&(t.class=n),this.newWindow&&(t.target="_blank",t.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length=s)return l.host.length==t?(l.host.substr(0,t-o)+n).substr(0,s+r):i(c,s).substr(0,s+r);var p="";if(l.path&&(p+="/"+l.path),l.query&&(p+="?"+l.query),p){if((c+p).length>=s)return(c+p).length==t?(c+p).substr(0,t):(c+i(p,s-c.length)).substr(0,s+r);c+=p}if(l.fragment){var f="#"+l.fragment;if((c+f).length>=s)return(c+f).length==t?(c+f).substr(0,t):(c+i(f,s-c.length)).substr(0,s+r);c+=f}if(l.scheme&&l.host){var h=l.scheme+"://";if((c+h).length0&&(d=c.substr(-1*Math.floor(s/2))),(c.substr(0,Math.ceil(s/2))+n+d).substr(0,s+r)}(e,n):"middle"===r?function(e,t,n){if(e.length<=t)return e;var r,o;null==n?(n="…",r=8,o=3):(r=n.length,o=n.length);var a=t-o,i="";return a>0&&(i=e.substr(-1*Math.floor(a/2))),(e.substr(0,Math.ceil(a/2))+n+i).substr(0,a+r)}(e,n):function(e,t,n){return function(e,t,n){var r;return e.length>t&&(null==n?(n="…",r=3):r=n.length,e=e.substring(0,t-r)+n),e}(e,t,n)}(e,n)},e}(),c=function(){function e(e){this.__jsduckDummyDocProp=null,this.matchedText="",this.offset=0,this.tagBuilder=e.tagBuilder,this.matchedText=e.matchedText,this.offset=e.offset}return e.prototype.getMatchedText=function(){return this.matchedText},e.prototype.setOffset=function(e){this.offset=e},e.prototype.getOffset=function(){return this.offset},e.prototype.getCssClassSuffixes=function(){return[this.getType()]},e.prototype.buildTag=function(){return this.tagBuilder.build(this)},e}(),p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},p(e,t)};function f(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var h=function(){return h=Object.assign||function(e){for(var t,n=1,r=arguments.length;n-1},e.isValidUriScheme=function(e){var t=e.match(this.uriSchemeRegex),n=t&&t[0].toLowerCase();return"javascript:"!==n&&"vbscript:"!==n},e.urlMatchDoesNotHaveProtocolOrDot=function(e,t){return!(!e||t&&this.hasFullProtocolRegex.test(t)||-1!==e.indexOf("."))},e.urlMatchDoesNotHaveAtLeastOneWordChar=function(e,t){return!(!e||!t)&&(!this.hasFullProtocolRegex.test(t)&&!this.hasWordCharAfterProtocolRegex.test(e))},e.hasFullProtocolRegex=/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,e.uriSchemeRegex=/^[A-Za-z][-.+A-Za-z0-9]*:/,e.hasWordCharAfterProtocolRegex=new RegExp(":[^\\s]*?["+k+"]"),e.ipRegex=/[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/,e}(),V=(d=new RegExp("[/?#](?:["+I+"\\-+&@#/%=~_()|'$*\\[\\]{}?!:,.;^✓]*["+I+"\\-+&@#/%=~_()|'$*\\[\\]{}✓])?"),new RegExp(["(?:","(",/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/.source,D(2),")","|","(","(//)?",/(?:www\.)/.source,D(6),")","|","(","(//)?",D(10)+"\\.",B.source,"(?![-"+T+"])",")",")","(?::[0-9]+)?","(?:"+d.source+")?"].join(""),"gi")),W=new RegExp("["+I+"]"),H=function(e){function t(t){var n=e.call(this,t)||this;return n.stripPrefix={scheme:!0,www:!0},n.stripTrailingSlash=!0,n.decodePercentEncoding=!0,n.matcherRegex=V,n.wordCharRegExp=W,n.stripPrefix=t.stripPrefix,n.stripTrailingSlash=t.stripTrailingSlash,n.decodePercentEncoding=t.decodePercentEncoding,n}return f(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.stripPrefix,o=this.stripTrailingSlash,a=this.decodePercentEncoding,i=this.tagBuilder,s=[],l=function(){var n=t[0],l=t[1],c=t[4],p=t[5],f=t[9],h=t.index,d=p||f,m=e.charAt(h-1);if(!$.isValid(n,l))return"continue";if(h>0&&"@"===m)return"continue";if(h>0&&d&&u.wordCharRegExp.test(m))return"continue";if(/\?$/.test(n)&&(n=n.substr(0,n.length-1)),u.matchHasUnbalancedClosingParen(n))n=n.substr(0,n.length-1);else{var g=u.matchHasInvalidCharAfterTld(n,l);g>-1&&(n=n.substr(0,g))}var y=["http://","https://"].find((function(e){return!!l&&-1!==l.indexOf(e)}));if(y){var v=n.indexOf(y);n=n.substr(v),l=l.substr(v),h+=v}var w=l?"scheme":c?"www":"tld",E=!!l;s.push(new b({tagBuilder:i,matchedText:n,offset:h,urlMatchType:w,url:n,protocolUrlMatch:E,protocolRelativeMatch:!!d,stripPrefix:r,stripTrailingSlash:o,decodePercentEncoding:a}))},u=this;null!==(t=n.exec(e));)l();return s},t.prototype.matchHasUnbalancedClosingParen=function(e){var t,n=e.charAt(e.length-1);if(")"===n)t="(";else if("]"===n)t="[";else{if("}"!==n)return!1;t="{"}for(var r=0,o=0,a=e.length-1;o-1&&a-i<=140){var o=e.slice(i,a),s=new g({tagBuilder:t,matchedText:o,offset:i,serviceName:n,hashtag:o.slice(1)});r.push(s)}}},t}(w),G=["twitter","facebook","instagram","tiktok"],Z=new RegExp("".concat(/(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/.source,"|").concat(/(0([1-9]{1}-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/.source),"g"),Y=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.matcherRegex=Z,t}return f(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.tagBuilder,o=[];null!==(t=n.exec(e));){var a=t[0],i=a.replace(/[^0-9,;#]/g,""),s=!(!t[1]&&!t[2]),l=0==t.index?"":e.substr(t.index-1,1),u=e.substr(t.index+a.length,1),c=!l.match(/\d/)&&!u.match(/\d/);this.testMatch(t[3])&&this.testMatch(a)&&c&&o.push(new v({tagBuilder:r,matchedText:a,offset:t.index,number:i,plusSign:s}))}return o},t.prototype.testMatch=function(e){return _.test(e)},t}(w),Q=new RegExp("@[_".concat(I,"]{1,50}(?![_").concat(I,"])"),"g"),X=new RegExp("@[_.".concat(I,"]{1,30}(?![_").concat(I,"])"),"g"),ee=new RegExp("@[-_.".concat(I,"]{1,50}(?![-_").concat(I,"])"),"g"),te=new RegExp("@[_.".concat(I,"]{1,23}[_").concat(I,"](?![_").concat(I,"])"),"g"),ne=new RegExp("[^"+I+"]"),re=function(e){function t(t){var n=e.call(this,t)||this;return n.serviceName="twitter",n.matcherRegexes={twitter:Q,instagram:X,soundcloud:ee,tiktok:te},n.nonWordCharRegex=ne,n.serviceName=t.serviceName,n}return f(t,e),t.prototype.parseMatches=function(e){var t,n=this.serviceName,r=this.matcherRegexes[this.serviceName],o=this.nonWordCharRegex,a=this.tagBuilder,i=[];if(!r)return i;for(;null!==(t=r.exec(e));){var s=t.index,l=e.charAt(s-1);if(0===s||o.test(l)){var u=t[0].replace(/\.+$/g,""),c=u.slice(1);i.push(new y({tagBuilder:a,matchedText:u,offset:s,serviceName:n,mention:c}))}}return i},t}(w);function oe(e,t){for(var n,r=t.onOpenTag,o=t.onCloseTag,a=t.onText,i=t.onComment,l=t.onDoctype,u=new ae,c=0,p=e.length,f=0,d=0,m=u;c"===e?(m=new ae(h(h({},m),{name:H()})),W()):E.test(e)||x.test(e)||":"===e||$()}function w(e){">"===e?$():E.test(e)?f=3:$()}function _(e){S.test(e)||("/"===e?f=12:">"===e?W():"<"===e?V():"="===e||A.test(e)||C.test(e)?$():f=5)}function k(e){S.test(e)?f=6:"/"===e?f=12:"="===e?f=7:">"===e?W():"<"===e?V():A.test(e)&&$()}function O(e){S.test(e)||("/"===e?f=12:"="===e?f=7:">"===e?W():"<"===e?V():A.test(e)?$():f=5)}function j(e){S.test(e)||('"'===e?f=8:"'"===e?f=9:/[>=`]/.test(e)?$():"<"===e?V():f=10)}function T(e){'"'===e&&(f=11)}function I(e){"'"===e&&(f=11)}function N(e){S.test(e)?f=4:">"===e?W():"<"===e&&V()}function P(e){S.test(e)?f=4:"/"===e?f=12:">"===e?W():"<"===e?V():(f=4,c--)}function R(e){">"===e?(m=new ae(h(h({},m),{isClosing:!0})),W()):f=4}function M(t){"--"===e.substr(c,2)?(c+=2,m=new ae(h(h({},m),{type:"comment"})),f=14):"DOCTYPE"===e.substr(c,7).toUpperCase()?(c+=7,m=new ae(h(h({},m),{type:"doctype"})),f=20):$()}function D(e){"-"===e?f=15:">"===e?$():f=16}function L(e){"-"===e?f=18:">"===e?$():f=16}function B(e){"-"===e&&(f=17)}function F(e){f="-"===e?18:16}function U(e){">"===e?W():"!"===e?f=19:"-"===e||(f=16)}function z(e){"-"===e?f=17:">"===e?W():f=16}function q(e){">"===e?W():"<"===e&&V()}function $(){f=0,m=u}function V(){f=1,m=new ae({idx:c})}function W(){var t=e.slice(d,m.idx);t&&a(t,d),"comment"===m.type?i(m.idx):"doctype"===m.type?l(m.idx):(m.isOpening&&r(m.name,m.idx),m.isClosing&&o(m.name,m.idx)),$(),d=c+1}function H(){var t=m.idx+(m.isClosing?2:1);return e.slice(t,c).toLowerCase()}d=0&&r++},onText:function(e,n){if(0===r){var a=function(e,t){if(!t.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var n,r=[],o=0;n=t.exec(e);)r.push(e.substring(o,n.index)),r.push(n[0]),o=n.index+n[0].length;return r.push(e.substring(o)),r}(e,/( | |<|<|>|>|"|"|')/gi),i=n;a.forEach((function(e,n){if(n%2==0){var r=t.parseText(e,i);o.push.apply(o,r)}i+=e.length}))}},onCloseTag:function(e){n.indexOf(e)>=0&&(r=Math.max(r-1,0))},onComment:function(e){},onDoctype:function(e){}}),o=this.compactMatches(o),o=this.removeUnwantedMatches(o)},e.prototype.compactMatches=function(e){e.sort((function(e,t){return e.getOffset()-t.getOffset()}));for(var t=0;to?t:t+1;e.splice(i,1);continue}if(e[t+1].getOffset()/g,">"));for(var t=this.parse(e),n=[],r=0,o=0,a=t.length;o/i.test(e)}function ue(){var e=[],t=new ie({stripPrefix:!1,url:!0,email:!0,replaceFn:function(t){switch(t.getType()){case"url":e.push({text:t.matchedText,url:t.getUrl()});break;case"email":e.push({text:t.matchedText,url:"mailto:"+t.getEmail().replace(/^mailto:/i,"")})}return!1}});return{links:e,autolinker:t}}function ce(e){var t,n,r,o,a,i,s,l,u,c,p,f,h,d,m=e.tokens,g=null;for(n=0,r=m.length;n=0;t--)if("link_close"!==(a=o[t]).type){if("htmltag"===a.type&&(d=a.content,/^\s]/i.test(d)&&p>0&&p--,le(a.content)&&p++),!(p>0)&&"text"===a.type&&se.test(a.content)){if(g||(f=(g=ue()).links,h=g.autolinker),i=a.content,f.length=0,h.link(i),!f.length)continue;for(s=[],c=a.level,l=0;l({useUnsafeMarkdown:!1})};const ye=ge;function ve(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=t,r=t?[]:["style","class"];return t&&!ve.hasWarnedAboutDeprecation&&(console.warn("useUnsafeMarkdown display configuration parameter is deprecated since >3.26.0 and will be removed in v4.0.0."),ve.hasWarnedAboutDeprecation=!0),he().sanitize(e,{ADD_ATTR:["target"],FORBID_TAGS:["style","form"],ALLOW_DATA_ATTR:n,FORBID_ATTR:r})}ve.hasWarnedAboutDeprecation=!1},45308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>f});var r,o=n(86),a=n.n(o),i=n(8712),s=n.n(i),l=n(90242),u=n(27621);const c=n(95102),p={},f=p;a()(r=s()(c).call(c)).call(r,(function(e){if("./index.js"===e)return;let t=c(e);p[(0,l.Zl)(e)]=t.default?t.default:t})),p.SafeRender=u.default},55812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{AUTHORIZE:()=>f,AUTHORIZE_OAUTH2:()=>m,CONFIGURE_AUTH:()=>y,LOGOUT:()=>h,PRE_AUTHORIZE_OAUTH2:()=>d,RESTORE_AUTHORIZATION:()=>v,SHOW_AUTH_POPUP:()=>p,VALIDATE:()=>g,authPopup:()=>M,authorize:()=>w,authorizeAccessCodeWithBasicAuthentication:()=>T,authorizeAccessCodeWithFormParams:()=>j,authorizeApplication:()=>O,authorizeOauth2:()=>A,authorizeOauth2WithPersistOption:()=>C,authorizePassword:()=>k,authorizeRequest:()=>I,authorizeWithPersistOption:()=>E,configureAuth:()=>N,logout:()=>x,logoutWithPersistOption:()=>_,persistAuthorizationIfNeeded:()=>R,preAuthorizeImplicit:()=>S,restoreAuthorization:()=>P,showDefinitions:()=>b});var r=n(35627),o=n.n(r),a=n(76986),i=n.n(a),s=n(84564),l=n.n(s),u=n(27504),c=n(90242);const p="show_popup",f="authorize",h="logout",d="pre_authorize_oauth2",m="authorize_oauth2",g="validate",y="configure_auth",v="restore_authorization";function b(e){return{type:p,payload:e}}function w(e){return{type:f,payload:e}}const E=e=>t=>{let{authActions:n}=t;n.authorize(e),n.persistAuthorizationIfNeeded()};function x(e){return{type:h,payload:e}}const _=e=>t=>{let{authActions:n}=t;n.logout(e),n.persistAuthorizationIfNeeded()},S=e=>t=>{let{authActions:n,errActions:r}=t,{auth:a,token:i,isValid:s}=e,{schema:l,name:c}=a,p=l.get("flow");delete u.Z.swaggerUIRedirectOauth2,"accessCode"===p||s||r.newAuthErr({authId:c,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),i.error?r.newAuthErr({authId:c,source:"auth",level:"error",message:o()(i)}):n.authorizeOauth2WithPersistOption({auth:a,token:i})};function A(e){return{type:m,payload:e}}const C=e=>t=>{let{authActions:n}=t;n.authorizeOauth2(e),n.persistAuthorizationIfNeeded()},k=e=>t=>{let{authActions:n}=t,{schema:r,name:o,username:a,password:s,passwordType:l,clientId:u,clientSecret:p}=e,f={grant_type:"password",scope:e.scopes.join(" "),username:a,password:s},h={};switch(l){case"request-body":!function(e,t,n){t&&i()(e,{client_id:t});n&&i()(e,{client_secret:n})}(f,u,p);break;case"basic":h.Authorization="Basic "+(0,c.r3)(u+":"+p);break;default:console.warn(`Warning: invalid passwordType ${l} was passed, not including client id and secret`)}return n.authorizeRequest({body:(0,c.GZ)(f),url:r.get("tokenUrl"),name:o,headers:h,query:{},auth:e})};const O=e=>t=>{let{authActions:n}=t,{schema:r,scopes:o,name:a,clientId:i,clientSecret:s}=e,l={Authorization:"Basic "+(0,c.r3)(i+":"+s)},u={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:(0,c.GZ)(u),name:a,url:r.get("tokenUrl"),auth:e,headers:l})},j=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:a,clientId:i,clientSecret:s,codeVerifier:l}=t,u={grant_type:"authorization_code",code:t.code,client_id:i,client_secret:s,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,c.GZ)(u),name:a,url:o.get("tokenUrl"),auth:t})}},T=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:a,clientId:i,clientSecret:s,codeVerifier:l}=t,u={Authorization:"Basic "+(0,c.r3)(i+":"+s)},p={grant_type:"authorization_code",code:t.code,client_id:i,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,c.GZ)(p),name:a,url:o.get("tokenUrl"),auth:t,headers:u})}},I=e=>t=>{let n,{fn:r,getConfigs:a,authActions:s,errActions:u,oas3Selectors:c,specSelectors:p,authSelectors:f}=t,{body:h,query:d={},headers:m={},name:g,url:y,auth:v}=e,{additionalQueryStringParams:b}=f.getConfigs()||{};if(p.isOAS3()){let e=c.serverEffectiveValue(c.selectedServer());n=l()(y,e,!0)}else n=l()(y,p.url(),!0);"object"==typeof b&&(n.query=i()({},n.query,b));const w=n.toString();let E=i()({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},m);r.fetch({url:w,method:"post",headers:E,query:d,body:h,requestInterceptor:a().requestInterceptor,responseInterceptor:a().responseInterceptor}).then((function(e){let t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?u.newAuthErr({authId:g,level:"error",source:"auth",message:o()(t)}):s.authorizeOauth2WithPersistOption({auth:v,token:t}):u.newAuthErr({authId:g,level:"error",source:"auth",message:e.statusText})})).catch((e=>{let t=new Error(e).message;if(e.response&&e.response.data){const n=e.response.data;try{const e="string"==typeof n?JSON.parse(n):n;e.error&&(t+=`, error: ${e.error}`),e.error_description&&(t+=`, description: ${e.error_description}`)}catch(e){}}u.newAuthErr({authId:g,level:"error",source:"auth",message:t})}))};function N(e){return{type:y,payload:e}}function P(e){return{type:v,payload:e}}const R=()=>e=>{let{authSelectors:t,getConfigs:n}=e;if(n().persistAuthorization){const e=t.authorized();localStorage.setItem("authorized",o()(e.toJS()))}},M=(e,t)=>()=>{u.Z.swaggerUIRedirectOauth2=t,u.Z.open(e)}},93705:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u,preauthorizeApiKey:()=>p,preauthorizeBasic:()=>c});var r=n(11189),o=n.n(r),a=n(43962),i=n(55812),s=n(60035),l=n(48302);function u(){return{afterLoad(e){this.rootInjects=this.rootInjects||{},this.rootInjects.initOAuth=e.authActions.configureAuth,this.rootInjects.preauthorizeApiKey=o()(p).call(p,null,e),this.rootInjects.preauthorizeBasic=o()(c).call(c,null,e)},statePlugins:{auth:{reducers:a.default,actions:i,selectors:s},spec:{wrapActions:l}}}}function c(e,t,n,r){const{authActions:{authorize:o},specSelectors:{specJson:a,isOAS3:i}}=e,s=i()?["components","securitySchemes"]:["securityDefinitions"],l=a().getIn([...s,t]);return l?o({[t]:{value:{username:n,password:r},schema:l.toJS()}}):null}function p(e,t,n){const{authActions:{authorize:r},specSelectors:{specJson:o,isOAS3:a}}=e,i=a()?["components","securitySchemes"]:["securityDefinitions"],s=o().getIn([...i,t]);return s?r({[t]:{value:n,schema:s.toJS()}}):null}},43962:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(86),o=n.n(r),a=n(76986),i=n.n(a),s=n(43393),l=n(90242),u=n(55812);const c={[u.SHOW_AUTH_POPUP]:(e,t)=>{let{payload:n}=t;return e.set("showDefinitions",n)},[u.AUTHORIZE]:(e,t)=>{var n;let{payload:r}=t,a=(0,s.fromJS)(r),i=e.get("authorized")||(0,s.Map)();return o()(n=a.entrySeq()).call(n,(t=>{let[n,r]=t;if(!(0,l.Wl)(r.getIn))return e.set("authorized",i);let o=r.getIn(["schema","type"]);if("apiKey"===o||"http"===o)i=i.set(n,r);else if("basic"===o){let e=r.getIn(["value","username"]),t=r.getIn(["value","password"]);i=i.setIn([n,"value"],{username:e,header:"Basic "+(0,l.r3)(e+":"+t)}),i=i.setIn([n,"schema"],r.get("schema"))}})),e.set("authorized",i)},[u.AUTHORIZE_OAUTH2]:(e,t)=>{let n,{payload:r}=t,{auth:o,token:a}=r;o.token=i()({},a),n=(0,s.fromJS)(o);let l=e.get("authorized")||(0,s.Map)();return l=l.set(n.get("name"),n),e.set("authorized",l)},[u.LOGOUT]:(e,t)=>{let{payload:n}=t,r=e.get("authorized").withMutations((e=>{o()(n).call(n,(t=>{e.delete(t)}))}));return e.set("authorized",r)},[u.CONFIGURE_AUTH]:(e,t)=>{let{payload:n}=t;return e.set("configs",n)},[u.RESTORE_AUTHORIZATION]:(e,t)=>{let{payload:n}=t;return e.set("authorized",(0,s.fromJS)(n.authorized))}}},60035:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorized:()=>x,definitionsForRequirements:()=>E,definitionsToAuthorize:()=>b,getConfigs:()=>S,getDefinitionsByNames:()=>w,isAuthorized:()=>_,shownDefinitions:()=>v});var r=n(86),o=n.n(r),a=n(51679),i=n.n(a),s=n(14418),l=n.n(s),u=n(11882),c=n.n(u),p=n(97606),f=n.n(p),h=n(28222),d=n.n(h),m=n(20573),g=n(43393);const y=e=>e,v=(0,m.P1)(y,(e=>e.get("showDefinitions"))),b=(0,m.P1)(y,(()=>e=>{var t;let{specSelectors:n}=e,r=n.securityDefinitions()||(0,g.Map)({}),a=(0,g.List)();return o()(t=r.entrySeq()).call(t,(e=>{let[t,n]=e,r=(0,g.Map)();r=r.set(t,n),a=a.push(r)})),a})),w=(e,t)=>e=>{var n;let{specSelectors:r}=e;console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.");let a=r.securityDefinitions(),i=(0,g.List)();return o()(n=t.valueSeq()).call(n,(e=>{var t;let n=(0,g.Map)();o()(t=e.entrySeq()).call(t,(e=>{let t,[r,i]=e,s=a.get(r);var l;"oauth2"===s.get("type")&&i.size&&(t=s.get("scopes"),o()(l=t.keySeq()).call(l,(e=>{i.contains(e)||(t=t.delete(e))})),s=s.set("allowedScopes",t));n=n.set(r,s)})),i=i.push(n)})),i},E=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,g.List)();return e=>{let{authSelectors:n}=e;const r=n.definitionsToAuthorize()||(0,g.List)();let a=(0,g.List)();return o()(r).call(r,(e=>{let n=i()(t).call(t,(t=>t.get(e.keySeq().first())));n&&(o()(e).call(e,((t,r)=>{if("oauth2"===t.get("type")){const i=n.get(r);let s=t.get("scopes");var a;if(g.List.isList(i)&&g.Map.isMap(s))o()(a=s.keySeq()).call(a,(e=>{i.contains(e)||(s=s.delete(e))})),e=e.set(r,t.set("scopes",s))}})),a=a.push(e))})),a}},x=(0,m.P1)(y,(e=>e.get("authorized")||(0,g.Map)())),_=(e,t)=>e=>{var n;let{authSelectors:r}=e,o=r.authorized();return g.List.isList(t)?!!l()(n=t.toJS()).call(n,(e=>{var t,n;return-1===c()(t=f()(n=d()(e)).call(n,(e=>!!o.get(e)))).call(t,!1)})).length:null},S=(0,m.P1)(y,(e=>e.get("configs")))},48302:(e,t,n)=>{"use strict";n.r(t),n.d(t,{execute:()=>r});const r=(e,t)=>{let{authSelectors:n,specSelectors:r}=t;return t=>{let{path:o,method:a,operation:i,extras:s}=t,l={authorized:n.authorized()&&n.authorized().toJS(),definitions:r.securityDefinitions()&&r.securityDefinitions().toJS(),specSecurity:r.security()&&r.security().toJS()};return e({path:o,method:a,operation:i,securities:l,...s})}}},70714:(e,t,n)=>{"use strict";n.r(t),n.d(t,{TOGGLE_CONFIGS:()=>o,UPDATE_CONFIGS:()=>r,loaded:()=>s,toggle:()=>i,update:()=>a});const r="configs_update",o="configs_toggle";function a(e,t){return{type:r,payload:{[e]:t}}}function i(e){return{type:o,payload:e}}const s=()=>e=>{let{getConfigs:t,authActions:n}=e;if(t().persistAuthorization){const e=localStorage.getItem("authorized");e&&n.restoreAuthorization({authorized:JSON.parse(e)})}}},92256:(e,t,n)=>{"use strict";n.r(t),n.d(t,{parseYamlConfig:()=>o});var r=n(1272);const o=(e,t)=>{try{return r.ZP.load(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}}},46709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(92256),o=n(70714),a=n(22698),i=n(69018),s=n(37743);const l={getLocalConfig:()=>(0,r.parseYamlConfig)('---\nurl: "https://petstore.swagger.io/v2/swagger.json"\ndom_id: "#swagger-ui"\nvalidatorUrl: "https://validator.swagger.io/validator"\n')};function u(){return{statePlugins:{spec:{actions:a,selectors:l},configs:{reducers:s.default,actions:o,selectors:i}}}}},37743:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(43393),o=n(70714);const a={[o.UPDATE_CONFIGS]:(e,t)=>e.merge((0,r.fromJS)(t.payload)),[o.TOGGLE_CONFIGS]:(e,t)=>{const n=t.payload,r=e.get(n);return e.set(n,!r)}}},69018:(e,t,n)=>{"use strict";n.r(t),n.d(t,{get:()=>a});var r=n(58309),o=n.n(r);const a=(e,t)=>e.getIn(o()(t)?t:[t])},22698:(e,t,n)=>{"use strict";n.r(t),n.d(t,{downloadConfig:()=>o,getConfigByUrl:()=>a});var r=n(92256);const o=e=>t=>{const{fn:{fetch:n}}=t;return n(e)},a=(e,t)=>n=>{let{specActions:o}=n;if(e)return o.downloadConfig(e).then(a,a);function a(n){n instanceof Error||n.status>=400?(o.updateLoadingStatus("failedConfig"),o.updateLoadingStatus("failedConfig"),o.updateUrl(""),console.error(n.statusText+" "+e.url),t(null)):t((0,r.parseYamlConfig)(n.text))}}},31970:(e,t,n)=>{"use strict";n.r(t),n.d(t,{setHash:()=>r});const r=e=>e?history.pushState(null,null,`#${e}`):window.location.hash=""},34980:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(41599),o=n(60877),a=n(34584);function i(){return[r.default,{statePlugins:{configs:{wrapActions:{loaded:(e,t)=>function(){e(...arguments);const n=decodeURIComponent(window.location.hash);t.layoutActions.parseDeepLinkHash(n)}}}},wrapComponents:{operation:o.default,OperationTag:a.default}}]}},41599:(e,t,n)=>{"use strict";n.r(t),n.d(t,{clearScrollTo:()=>S,default:()=>A,parseDeepLinkHash:()=>E,readyToScroll:()=>x,scrollTo:()=>w,scrollToElement:()=>_,show:()=>b});var r=n(58309),o=n.n(r),a=n(24278),i=n.n(a),s=n(97606),l=n.n(s),u=n(11882),c=n.n(u),p=n(31970),f=n(45172),h=n.n(f),d=n(90242),m=n(43393),g=n.n(m);const y="layout_scroll_to",v="layout_clear_scroll",b=(e,t)=>{let{getConfigs:n,layoutSelectors:r}=t;return function(){for(var t=arguments.length,a=new Array(t),i=0;i({type:y,payload:o()(e)?e:[e]}),E=e=>t=>{let{layoutActions:n,layoutSelectors:r,getConfigs:o}=t;if(o().deepLinking&&e){var a;let t=i()(e).call(e,1);"!"===t[0]&&(t=i()(t).call(t,1)),"/"===t[0]&&(t=i()(t).call(t,1));const o=l()(a=t.split("/")).call(a,(e=>e||"")),s=r.isShownKeyFromUrlHashArray(o),[u,p="",f=""]=s;if("operations"===u){const e=r.isShownKeyFromUrlHashArray([p]);c()(p).call(p,"_")>-1&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(e).call(e,(e=>e.replace(/_/g," "))),!0)),n.show(e,!0)}(c()(p).call(p,"_")>-1||c()(f).call(f,"_")>-1)&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(s).call(s,(e=>e.replace(/_/g," "))),!0)),n.show(s,!0),n.scrollTo(s)}},x=(e,t)=>n=>{const r=n.layoutSelectors.getScrollToKey();g().is(r,(0,m.fromJS)(e))&&(n.layoutActions.scrollToElement(t),n.layoutActions.clearScrollTo())},_=(e,t)=>n=>{try{t=t||n.fn.getScrollParent(e),h().createScroller(t).to(e)}catch(e){console.error(e)}},S=()=>({type:v});const A={fn:{getScrollParent:function(e,t){const n=document.documentElement;let r=getComputedStyle(e);const o="absolute"===r.position,a=t?/(auto|scroll|hidden)/:/(auto|scroll)/;if("fixed"===r.position)return n;for(let t=e;t=t.parentElement;)if(r=getComputedStyle(t),(!o||"static"!==r.position)&&a.test(r.overflow+r.overflowY+r.overflowX))return t;return n}},statePlugins:{layout:{actions:{scrollToElement:_,scrollTo:w,clearScrollTo:S,readyToScroll:x,parseDeepLinkHash:E},selectors:{getScrollToKey:e=>e.get("scrollToKey"),isShownKeyFromUrlHashArray(e,t){const[n,r]=t;return r?["operations",n,r]:n?["operations-tag",n]:[]},urlHashArrayFromIsShownKey(e,t){let[n,r,o]=t;return"operations"==n?[r,o]:"operations-tag"==n?[r]:[]}},reducers:{[y]:(e,t)=>e.set("scrollToKey",g().fromJS(t.payload)),[v]:e=>e.delete("scrollToKey")},wrapActions:{show:b}}}}},34584:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);const i=(e,t)=>class extends a.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{tag:n}=this.props,r=["operations-tag",n];t.layoutActions.readyToScroll(r,e)}))}render(){return a.createElement("span",{ref:this.onLoad},a.createElement(e,this.props))}}},60877:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);n(23930);const i=(e,t)=>class extends a.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{operation:n}=this.props,{tag:r,operationId:o}=n.toObject();let{isShownKey:a}=n.toObject();a=a||["operations",r,o],t.layoutActions.readyToScroll(a,e)}))}render(){return a.createElement("span",{ref:this.onLoad},a.createElement(e,this.props))}}},48011:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(76986),o=n.n(r),a=n(63460),i=n.n(a),s=n(11882),l=n.n(s),u=n(35627),c=n.n(u),p=n(20573),f=n(43393),h=n(27504);function d(e){let{fn:t}=e;return{statePlugins:{spec:{actions:{download:e=>n=>{let{errActions:r,specSelectors:a,specActions:s,getConfigs:l}=n,{fetch:u}=t;const c=l();function p(t){if(t instanceof Error||t.status>=400)return s.updateLoadingStatus("failed"),r.newThrownErr(o()(new Error((t.message||t.statusText)+" "+e),{source:"fetch"})),void(!t.status&&t instanceof Error&&function(){try{let t;if("URL"in h.Z?t=new(i())(e):(t=document.createElement("a"),t.href=e),"https:"!==t.protocol&&"https:"===h.Z.location.protocol){const e=o()(new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${t.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),{source:"fetch"});return void r.newThrownErr(e)}if(t.origin!==h.Z.location.origin){const e=o()(new Error(`Possible cross-origin (CORS) issue? The URL origin (${t.origin}) does not match the page (${h.Z.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),{source:"fetch"});r.newThrownErr(e)}}catch(e){return}}());s.updateLoadingStatus("success"),s.updateSpec(t.text),a.url()!==e&&s.updateUrl(e)}e=e||a.url(),s.updateLoadingStatus("loading"),r.clear({source:"fetch"}),u({url:e,loadSpec:!0,requestInterceptor:c.requestInterceptor||(e=>e),responseInterceptor:c.responseInterceptor||(e=>e),credentials:"same-origin",headers:{Accept:"application/json,*/*"}}).then(p,p)},updateLoadingStatus:e=>{let t=[null,"loading","failed","success","failedConfig"];return-1===l()(t).call(t,e)&&console.error(`Error: ${e} is not one of ${c()(t)}`),{type:"spec_update_loading_status",payload:e}}},reducers:{spec_update_loading_status:(e,t)=>"string"==typeof t.payload?e.set("loadingStatus",t.payload):e},selectors:{loadingStatus:(0,p.P1)((e=>e||(0,f.Map)()),(e=>e.get("loadingStatus")||null))}}}}}},34966:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR:()=>u,CLEAR_BY:()=>c,NEW_AUTH_ERR:()=>l,NEW_SPEC_ERR:()=>i,NEW_SPEC_ERR_BATCH:()=>s,NEW_THROWN_ERR:()=>o,NEW_THROWN_ERR_BATCH:()=>a,clear:()=>g,clearBy:()=>y,newAuthErr:()=>m,newSpecErr:()=>h,newSpecErrBatch:()=>d,newThrownErr:()=>p,newThrownErrBatch:()=>f});var r=n(7710);const o="err_new_thrown_err",a="err_new_thrown_err_batch",i="err_new_spec_err",s="err_new_spec_err_batch",l="err_new_auth_err",u="err_clear",c="err_clear_by";function p(e){return{type:o,payload:(0,r.serializeError)(e)}}function f(e){return{type:a,payload:e}}function h(e){return{type:i,payload:e}}function d(e){return{type:s,payload:e}}function m(e){return{type:l,payload:e}}function g(){return{type:u,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}}}function y(){return{type:c,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:()=>!0}}},56982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(14418),o=n.n(r),a=n(97606),i=n.n(a),s=n(54061),l=n.n(s);const u=[n(2392),n(21835)];function c(e){var t;let n={jsSpec:{}},r=l()(u,((e,t)=>{try{let r=t.transform(e,n);return o()(r).call(r,(e=>!!e))}catch(t){return console.error("Transformer error:",t),e}}),e);return i()(t=o()(r).call(r,(e=>!!e))).call(t,(e=>(!e.get("line")&&e.get("path"),e)))}},2392:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>p});var r=n(97606),o=n.n(r),a=n(11882),i=n.n(a),s=n(24278),l=n.n(s),u=n(24282),c=n.n(u);function p(e){return o()(e).call(e,(e=>{var t;let n="is not of a type(s)",r=i()(t=e.get("message")).call(t,n);if(r>-1){var o,a;let t=l()(o=e.get("message")).call(o,r+n.length).split(",");return e.set("message",l()(a=e.get("message")).call(a,0,r)+function(e){return c()(e).call(e,((e,t,n,r)=>n===r.length-1&&r.length>1?e+"or "+t:r[n+1]&&r.length>2?e+t+", ":r[n+1]?e+t+" ":e+t),"should be a")}(t))}return e}))}},21835:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>r});n(97606),n(11882),n(27361),n(43393);function r(e,t){let{jsSpec:n}=t;return e}},77793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(93527),o=n(34966),a=n(87667);function i(e){return{statePlugins:{err:{reducers:(0,r.default)(e),actions:o,selectors:a}}}}},93527:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(76986),o=n.n(r),a=n(97606),i=n.n(a),s=n(39022),l=n.n(s),u=n(14418),c=n.n(u),p=n(2250),f=n.n(p),h=n(34966),d=n(43393),m=n(56982);let g={line:0,level:"error",message:"Unknown error"};function y(){return{[h.NEW_THROWN_ERR]:(e,t)=>{let{payload:n}=t,r=o()(g,n,{type:"thrown"});return e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[h.NEW_THROWN_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"thrown"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[h.NEW_SPEC_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(n);return r=r.set("type","spec"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)).sortBy((e=>e.get("line"))))).update("errors",(e=>(0,m.default)(e)))},[h.NEW_SPEC_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"spec"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[h.NEW_AUTH_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(o()({},n));return r=r.set("type","auth"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[h.CLEAR]:(e,t)=>{var n;let{payload:r}=t;if(!r||!e.get("errors"))return e;let o=c()(n=e.get("errors")).call(n,(e=>{var t;return f()(t=e.keySeq()).call(t,(t=>{const n=e.get(t),o=r[t];return!o||n!==o}))}));return e.merge({errors:o})},[h.CLEAR_BY]:(e,t)=>{var n;let{payload:r}=t;if(!r||"function"!=typeof r)return e;let o=c()(n=e.get("errors")).call(n,(e=>r(e)));return e.merge({errors:o})}}}},87667:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allErrors:()=>a,lastError:()=>i});var r=n(43393),o=n(20573);const a=(0,o.P1)((e=>e),(e=>e.get("errors",(0,r.List)()))),i=(0,o.P1)(a,(e=>e.last()))},49978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4309);function o(){return{fn:{opsFilter:r.default}}}},4309:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(14418),o=n.n(r),a=n(11882),i=n.n(a);function s(e,t){return o()(e).call(e,((e,n)=>-1!==i()(n).call(n,t)))}},25474:(e,t,n)=>{"use strict";n.r(t),n.d(t,{SHOW:()=>s,UPDATE_FILTER:()=>a,UPDATE_LAYOUT:()=>o,UPDATE_MODE:()=>i,changeMode:()=>p,show:()=>c,updateFilter:()=>u,updateLayout:()=>l});var r=n(90242);const o="layout_update_layout",a="layout_update_filter",i="layout_update_mode",s="layout_show";function l(e){return{type:o,payload:e}}function u(e){return{type:a,payload:e}}function c(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e=(0,r.AF)(e),{type:s,payload:{thing:e,shown:t}}}function p(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.AF)(e),{type:i,payload:{thing:e,mode:t}}}},26821:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(5672),o=n(25474),a=n(4400),i=n(28989);function s(){return{statePlugins:{layout:{reducers:r.default,actions:o,selectors:a},spec:{wrapSelectors:i}}}}},5672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(39022),o=n.n(r),a=n(43393),i=n(25474);const s={[i.UPDATE_LAYOUT]:(e,t)=>e.set("layout",t.payload),[i.UPDATE_FILTER]:(e,t)=>e.set("filter",t.payload),[i.SHOW]:(e,t)=>{const n=t.payload.shown,r=(0,a.fromJS)(t.payload.thing);return e.update("shown",(0,a.fromJS)({}),(e=>e.set(r,n)))},[i.UPDATE_MODE]:(e,t)=>{var n;let r=t.payload.thing,a=t.payload.mode;return e.setIn(o()(n=["modes"]).call(n,r),(a||"")+"")}}},4400:(e,t,n)=>{"use strict";n.r(t),n.d(t,{current:()=>i,currentFilter:()=>s,isShown:()=>l,showSummary:()=>c,whatMode:()=>u});var r=n(20573),o=n(90242),a=n(43393);const i=e=>e.get("layout"),s=e=>e.get("filter"),l=(e,t,n)=>(t=(0,o.AF)(t),e.get("shown",(0,a.fromJS)({})).get((0,a.fromJS)(t),n)),u=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return t=(0,o.AF)(t),e.getIn(["modes",...t],n)},c=(0,r.P1)((e=>e),(e=>!l(e,"editor")))},28989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{taggedOperations:()=>a});var r=n(24278),o=n.n(r);const a=(e,t)=>function(n){for(var r=arguments.length,a=new Array(r>1?r-1:0),i=1;i=0&&(s=o()(s).call(s,0,f)),s}},9150:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(11189),o=n.n(r);function a(e){let{configs:t}=e;const n={debug:0,info:1,log:2,warn:3,error:4},r=e=>n[e]||-1;let{logLevel:a}=t,i=r(a);function s(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o=i&&console[e](...n)}return s.warn=o()(s).call(s,null,"warn"),s.error=o()(s).call(s,null,"error"),s.info=o()(s).call(s,null,"info"),s.debug=o()(s).call(s,null,"debug"),{rootInjects:{log:s}}}},67002:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST_BODY_VALIDATE_ERROR:()=>f,CLEAR_REQUEST_BODY_VALUE:()=>h,SET_REQUEST_BODY_VALIDATE_ERROR:()=>p,UPDATE_ACTIVE_EXAMPLES_MEMBER:()=>s,UPDATE_REQUEST_BODY_INCLUSION:()=>i,UPDATE_REQUEST_BODY_VALUE:()=>o,UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG:()=>a,UPDATE_REQUEST_CONTENT_TYPE:()=>l,UPDATE_RESPONSE_CONTENT_TYPE:()=>u,UPDATE_SELECTED_SERVER:()=>r,UPDATE_SERVER_VARIABLE_VALUE:()=>c,clearRequestBodyValidateError:()=>_,clearRequestBodyValue:()=>A,initRequestBodyValidateError:()=>S,setActiveExamplesMember:()=>v,setRequestBodyInclusion:()=>y,setRequestBodyValidateError:()=>x,setRequestBodyValue:()=>m,setRequestContentType:()=>b,setResponseContentType:()=>w,setRetainRequestBodyValueFlag:()=>g,setSelectedServer:()=>d,setServerVariableValue:()=>E});const r="oas3_set_servers",o="oas3_set_request_body_value",a="oas3_set_request_body_retain_flag",i="oas3_set_request_body_inclusion",s="oas3_set_active_examples_member",l="oas3_set_request_content_type",u="oas3_set_response_content_type",c="oas3_set_server_variable_value",p="oas3_set_request_body_validate_error",f="oas3_clear_request_body_validate_error",h="oas3_clear_request_body_value";function d(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}}function m(e){let{value:t,pathMethod:n}=e;return{type:o,payload:{value:t,pathMethod:n}}}const g=e=>{let{value:t,pathMethod:n}=e;return{type:a,payload:{value:t,pathMethod:n}}};function y(e){let{value:t,pathMethod:n,name:r}=e;return{type:i,payload:{value:t,pathMethod:n,name:r}}}function v(e){let{name:t,pathMethod:n,contextType:r,contextName:o}=e;return{type:s,payload:{name:t,pathMethod:n,contextType:r,contextName:o}}}function b(e){let{value:t,pathMethod:n}=e;return{type:l,payload:{value:t,pathMethod:n}}}function w(e){let{value:t,path:n,method:r}=e;return{type:u,payload:{value:t,path:n,method:r}}}function E(e){let{server:t,namespace:n,key:r,val:o}=e;return{type:c,payload:{server:t,namespace:n,key:r,val:o}}}const x=e=>{let{path:t,method:n,validationErrors:r}=e;return{type:p,payload:{path:t,method:n,validationErrors:r}}},_=e=>{let{path:t,method:n}=e;return{type:f,payload:{path:t,method:n}}},S=e=>{let{pathMethod:t}=e;return{type:f,payload:{path:t[0],method:t[1]}}},A=e=>{let{pathMethod:t}=e;return{type:h,payload:{pathMethod:t}}}},73723:(e,t,n)=>{"use strict";n.r(t),n.d(t,{definitionsToAuthorize:()=>f});var r=n(86),o=n.n(r),a=n(14418),i=n.n(a),s=n(24282),l=n.n(s),u=n(20573),c=n(43393),p=n(7779);const f=(h=(0,u.P1)((e=>e),(e=>{let{specSelectors:t}=e;return t.securityDefinitions()}),((e,t)=>{var n;let r=(0,c.List)();return t?(o()(n=t.entrySeq()).call(n,(e=>{let[t,n]=e;const a=n.get("type");var s;if("oauth2"===a&&o()(s=n.get("flows").entrySeq()).call(s,(e=>{let[o,a]=e,s=(0,c.fromJS)({flow:o,authorizationUrl:a.get("authorizationUrl"),tokenUrl:a.get("tokenUrl"),scopes:a.get("scopes"),type:n.get("type"),description:n.get("description")});r=r.push(new c.Map({[t]:i()(s).call(s,(e=>void 0!==e))}))})),"http"!==a&&"apiKey"!==a||(r=r.push(new c.Map({[t]:n}))),"openIdConnect"===a&&n.get("openIdConnectData")){let e=n.get("openIdConnectData"),a=e.get("grant_types_supported")||["authorization_code","implicit"];o()(a).call(a,(o=>{var a;let s=e.get("scopes_supported")&&l()(a=e.get("scopes_supported")).call(a,((e,t)=>e.set(t,"")),new c.Map),u=(0,c.fromJS)({flow:o,authorizationUrl:e.get("authorization_endpoint"),tokenUrl:e.get("token_endpoint"),scopes:s,type:"oauth2",openIdConnectUrl:n.get("openIdConnectUrl")});r=r.push(new c.Map({[t]:i()(u).call(u,(e=>void 0!==e))}))}))}})),r):r})),(e,t)=>function(){const n=t.getSystem().specSelectors.specJson();for(var r=arguments.length,o=new Array(r),a=0;a{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(23101),o=n.n(r),a=n(97606),i=n.n(a),s=n(67294),l=(n(23930),n(43393));const u=e=>{var t;let{callbacks:n,getComponent:r,specPath:a}=e;const u=r("OperationContainer",!0);if(!n)return s.createElement("span",null,"No callbacks");let c=i()(t=n.entrySeq()).call(t,(t=>{var n;let[r,c]=t;return s.createElement("div",{key:r},s.createElement("h2",null,r),i()(n=c.entrySeq()).call(n,(t=>{var n;let[c,p]=t;return"$$ref"===c?null:s.createElement("div",{key:c},i()(n=p.entrySeq()).call(n,(t=>{let[n,i]=t;if("$$ref"===n)return null;let p=(0,l.fromJS)({operation:i});return s.createElement(u,o()({},e,{op:p,key:n,tag:"",method:n,path:c,specPath:a.push(r,c,n),allowTryItOut:!1}))})))})))}));return s.createElement("div",null,c)}},86775:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>f});var r=n(61125),o=n.n(r),a=n(76986),i=n.n(a),s=n(14418),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294);class f extends p.Component{constructor(e,t){super(e,t),o()(this,"onChange",(e=>{let{onChange:t}=this.props,{value:n,name:r}=e.target,o=i()({},this.state.value);r?o[r]=n:o=n,this.setState({value:o},(()=>t(this.state)))}));let{name:n,schema:r}=this.props,a=this.getValue();this.state={name:n,schema:r,value:a}}getValue(){let{name:e,authorized:t}=this.props;return t&&t.getIn([e,"value"])}render(){var e;let{schema:t,getComponent:n,errSelectors:r,name:o}=this.props;const a=n("Input"),i=n("Row"),s=n("Col"),u=n("authError"),f=n("Markdown",!0),h=n("JumpToPath",!0),d=(t.get("scheme")||"").toLowerCase();let m=this.getValue(),g=l()(e=r.allErrors()).call(e,(e=>e.get("authId")===o));if("basic"===d){var y;let e=m?m.get("username"):null;return p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Basic)",p.createElement(h,{path:["securityDefinitions",o]})),e&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(f,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Username:"),e?p.createElement("code",null," ",e," "):p.createElement(s,null,p.createElement(a,{type:"text",required:"required",name:"username","aria-label":"auth-basic-username",onChange:this.onChange,autoFocus:!0}))),p.createElement(i,null,p.createElement("label",null,"Password:"),e?p.createElement("code",null," ****** "):p.createElement(s,null,p.createElement(a,{autoComplete:"new-password",name:"password",type:"password","aria-label":"auth-basic-password",onChange:this.onChange}))),c()(y=g.valueSeq()).call(y,((e,t)=>p.createElement(u,{error:e,key:t}))))}var v;return"bearer"===d?p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Bearer)",p.createElement(h,{path:["securityDefinitions",o]})),m&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(f,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Value:"),m?p.createElement("code",null," ****** "):p.createElement(s,null,p.createElement(a,{type:"text","aria-label":"auth-bearer-value",onChange:this.onChange,autoFocus:!0}))),c()(v=g.valueSeq()).call(v,((e,t)=>p.createElement(u,{error:e,key:t})))):p.createElement("div",null,p.createElement("em",null,p.createElement("b",null,o)," HTTP authentication: unsupported scheme ",`'${d}'`))}}},76467:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(33427),o=n(42458),a=n(15757),i=n(56617),s=n(9928),l=n(45327),u=n(86775),c=n(96796);const p={Callbacks:r.default,HttpAuth:u.default,RequestBody:o.default,Servers:i.default,ServersContainer:s.default,RequestBodyEditor:l.default,OperationServers:c.default,operationLink:a.default}},15757:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(35627),o=n.n(r),a=n(97606),i=n.n(a),s=n(67294);n(23930);class l extends s.Component{render(){const{link:e,name:t,getComponent:n}=this.props,r=n("Markdown",!0);let a=e.get("operationId")||e.get("operationRef"),l=e.get("parameters")&&e.get("parameters").toJS(),u=e.get("description");return s.createElement("div",{className:"operation-link"},s.createElement("div",{className:"description"},s.createElement("b",null,s.createElement("code",null,t)),u?s.createElement(r,{source:u}):null),s.createElement("pre",null,"Operation `",a,"`",s.createElement("br",null),s.createElement("br",null),"Parameters ",function(e,t){var n;if("string"!=typeof t)return"";return i()(n=t.split("\n")).call(n,((t,n)=>n>0?Array(e+1).join(" ")+t:t)).join("\n")}(0,o()(l,null,2))||"{}",s.createElement("br",null)))}}const u=l},96796:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),a=n(67294);n(23930);class i extends a.Component{constructor(){super(...arguments),o()(this,"setSelectedServer",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setSelectedServer(e,`${t}:${n}`)})),o()(this,"setServerVariableValue",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setServerVariableValue({...e,namespace:`${t}:${n}`})})),o()(this,"getSelectedServer",(()=>{const{path:e,method:t}=this.props;return this.props.getSelectedServer(`${e}:${t}`)})),o()(this,"getServerVariable",((e,t)=>{const{path:n,method:r}=this.props;return this.props.getServerVariable({namespace:`${n}:${r}`,server:e},t)})),o()(this,"getEffectiveServerValue",(e=>{const{path:t,method:n}=this.props;return this.props.getEffectiveServerValue({server:e,namespace:`${t}:${n}`})}))}render(){const{operationServers:e,pathServers:t,getComponent:n}=this.props;if(!e&&!t)return null;const r=n("Servers"),o=e||t,i=e?"operation":"path";return a.createElement("div",{className:"opblock-section operation-servers"},a.createElement("div",{className:"opblock-section-header"},a.createElement("div",{className:"tab-header"},a.createElement("h4",{className:"opblock-title"},"Servers"))),a.createElement("div",{className:"opblock-description-wrapper"},a.createElement("h4",{className:"message"},"These ",i,"-level options override the global server options."),a.createElement(r,{servers:o,currentServer:this.getSelectedServer(),setSelectedServer:this.setSelectedServer,setServerVariableValue:this.setServerVariableValue,getServerVariable:this.getServerVariable,getEffectiveServerValue:this.getEffectiveServerValue})))}}},45327:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(61125),o=n.n(r),a=n(67294),i=n(94184),s=n.n(i),l=n(90242);const u=Function.prototype;class c extends a.PureComponent{constructor(e,t){super(e,t),o()(this,"applyDefaultValue",(e=>{const{onChange:t,defaultValue:n}=e||this.props;return this.setState({value:n}),t(n)})),o()(this,"onChange",(e=>{this.props.onChange((0,l.Pz)(e))})),o()(this,"onDomChange",(e=>{const t=e.target.value;this.setState({value:t},(()=>this.onChange(t)))})),this.state={value:(0,l.Pz)(e.value)||e.defaultValue},e.onChange(e.value)}UNSAFE_componentWillReceiveProps(e){this.props.value!==e.value&&e.value!==this.state.value&&this.setState({value:(0,l.Pz)(e.value)}),!e.value&&e.defaultValue&&this.state.value&&this.applyDefaultValue(e)}render(){let{getComponent:e,errors:t}=this.props,{value:n}=this.state,r=t.size>0;const o=e("TextArea");return a.createElement("div",{className:"body-param"},a.createElement(o,{className:s()("body-param__text",{invalid:r}),title:t.size?t.join(", "):"",value:n,onChange:this.onDomChange}))}}o()(c,"defaultProps",{onChange:u,userHasEditedBody:!1})},42458:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g,getDefaultRequestBodyValue:()=>m});var r=n(97606),o=n.n(r),a=n(11882),i=n.n(a),s=n(58118),l=n.n(s),u=n(58309),c=n.n(u),p=n(67294),f=(n(23930),n(43393)),h=n(90242),d=n(2518);const m=(e,t,n)=>{const r=e.getIn(["content",t]),o=r.get("schema").toJS(),a=void 0!==r.get("examples"),i=r.get("example"),s=a?r.getIn(["examples",n,"value"]):i,l=(0,h.xi)(o,t,{includeWriteOnly:!0},s);return(0,h.Pz)(l)},g=e=>{let{userHasEditedBody:t,requestBody:n,requestBodyValue:r,requestBodyInclusionSetting:a,requestBodyErrors:s,getComponent:u,getConfigs:g,specSelectors:y,fn:v,contentType:b,isExecute:w,specPath:E,onChange:x,onChangeIncludeEmpty:_,activeExamplesKey:S,updateActiveExamplesKey:A,setRetainRequestBodyValueFlag:C}=e;const k=e=>{x(e.target.files[0])},O=e=>{let t={key:e,shouldDispatchInit:!1,defaultValue:!0};return"no value"===a.get(e,"no value")&&(t.shouldDispatchInit=!0),t},j=u("Markdown",!0),T=u("modelExample"),I=u("RequestBodyEditor"),N=u("highlightCode"),P=u("ExamplesSelectValueRetainer"),R=u("Example"),M=u("ParameterIncludeEmpty"),{showCommonExtensions:D}=g(),L=n&&n.get("description")||null,B=n&&n.get("content")||new f.OrderedMap;b=b||B.keySeq().first()||"";const F=B.get(b,(0,f.OrderedMap)()),U=F.get("schema",(0,f.OrderedMap)()),z=F.get("examples",null),q=null==z?void 0:o()(z).call(z,((e,t)=>{var r;const o=null===(r=e)||void 0===r?void 0:r.get("value",null);return o&&(e=e.set("value",m(n,b,t),o)),e}));if(s=f.List.isList(s)?s:(0,f.List)(),!F.size)return null;const $="object"===F.getIn(["schema","type"]),V="binary"===F.getIn(["schema","format"]),W="base64"===F.getIn(["schema","format"]);if("application/octet-stream"===b||0===i()(b).call(b,"image/")||0===i()(b).call(b,"audio/")||0===i()(b).call(b,"video/")||V||W){const e=u("Input");return w?p.createElement(e,{type:"file",onChange:k}):p.createElement("i",null,"Example values are not available for ",p.createElement("code",null,b)," media types.")}if($&&("application/x-www-form-urlencoded"===b||0===i()(b).call(b,"multipart/"))&&U.get("properties",(0,f.OrderedMap)()).size>0){var H;const e=u("JsonSchemaForm"),t=u("ParameterExt"),n=U.get("properties",(0,f.OrderedMap)());return r=f.Map.isMap(r)?r:(0,f.OrderedMap)(),p.createElement("div",{className:"table-container"},L&&p.createElement(j,{source:L}),p.createElement("table",null,p.createElement("tbody",null,f.Map.isMap(n)&&o()(H=n.entrySeq()).call(H,(n=>{var i,d;let[m,g]=n;if(g.get("readOnly"))return;let y=D?(0,h.po)(g):null;const b=l()(i=U.get("required",(0,f.List)())).call(i,m),E=g.get("type"),S=g.get("format"),A=g.get("description"),C=r.getIn([m,"value"]),k=r.getIn([m,"errors"])||s,T=a.get(m)||!1,I=g.has("default")||g.has("example")||g.hasIn(["items","example"])||g.hasIn(["items","default"]),N=g.has("enum")&&(1===g.get("enum").size||b),P=I||N;let R="";"array"!==E||P||(R=[]),("object"===E||P)&&(R=(0,h.xi)(g,!1,{includeWriteOnly:!0})),"string"!=typeof R&&"object"===E&&(R=(0,h.Pz)(R)),"string"==typeof R&&"array"===E&&(R=JSON.parse(R));const L="string"===E&&("binary"===S||"base64"===S);return p.createElement("tr",{key:m,className:"parameters","data-property-name":m},p.createElement("td",{className:"parameters-col_name"},p.createElement("div",{className:b?"parameter__name required":"parameter__name"},m,b?p.createElement("span",null," *"):null),p.createElement("div",{className:"parameter__type"},E,S&&p.createElement("span",{className:"prop-format"},"($",S,")"),D&&y.size?o()(d=y.entrySeq()).call(d,(e=>{let[n,r]=e;return p.createElement(t,{key:`${n}-${r}`,xKey:n,xVal:r})})):null),p.createElement("div",{className:"parameter__deprecated"},g.get("deprecated")?"deprecated":null)),p.createElement("td",{className:"parameters-col_description"},p.createElement(j,{source:A}),w?p.createElement("div",null,p.createElement(e,{fn:v,dispatchInitialValue:!L,schema:g,description:m,getComponent:u,value:void 0===C?R:C,required:b,errors:k,onChange:e=>{x(e,[m])}}),b?null:p.createElement(M,{onChange:e=>_(m,e),isIncluded:T,isIncludedOptions:O(m),isDisabled:c()(C)?0!==C.length:!(0,h.O2)(C)})):null))})))))}const J=m(n,b,S);let K=null;return(0,d.O)(J)&&(K="json"),p.createElement("div",null,L&&p.createElement(j,{source:L}),q?p.createElement(P,{userHasEditedBody:t,examples:q,currentKey:S,currentUserInputValue:r,onSelect:e=>{A(e)},updateValue:x,defaultToFirstExample:!0,getComponent:u,setRetainRequestBodyValueFlag:C}):null,w?p.createElement("div",null,p.createElement(I,{value:r,errors:s,defaultValue:J,onChange:x,getComponent:u})):p.createElement(T,{getComponent:u,getConfigs:g,specSelectors:y,expandDepth:1,isExecute:w,schema:F.get("schema"),specPath:E.push("content",b),example:p.createElement(N,{className:"body-param__example",getConfigs:g,language:K,value:(0,h.Pz)(r)||J}),includeWriteOnly:!0}),q?p.createElement(R,{example:q.get(S),getComponent:u,getConfigs:g}):null)}},9928:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);class o extends r.Component{render(){const{specSelectors:e,oas3Selectors:t,oas3Actions:n,getComponent:o}=this.props,a=e.servers(),i=o("Servers");return a&&a.size?r.createElement("div",null,r.createElement("span",{className:"servers-title"},"Servers"),r.createElement(i,{servers:a,currentServer:t.selectedServer(),setSelectedServer:n.setSelectedServer,setServerVariableValue:n.setServerVariableValue,getServerVariable:t.serverVariableValue,getEffectiveServerValue:t.serverEffectiveValue})):null}}},56617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(61125),o=n.n(r),a=n(51679),i=n.n(a),s=n(97606),l=n.n(s),u=n(67294),c=n(43393);n(23930);class p extends u.Component{constructor(){super(...arguments),o()(this,"onServerChange",(e=>{this.setServer(e.target.value)})),o()(this,"onServerVariableValueChange",(e=>{let{setServerVariableValue:t,currentServer:n}=this.props,r=e.target.getAttribute("data-variable"),o=e.target.value;"function"==typeof t&&t({server:n,key:r,val:o})})),o()(this,"setServer",(e=>{let{setSelectedServer:t}=this.props;t(e)}))}componentDidMount(){var e;let{servers:t,currentServer:n}=this.props;n||this.setServer(null===(e=t.first())||void 0===e?void 0:e.get("url"))}UNSAFE_componentWillReceiveProps(e){let{servers:t,setServerVariableValue:n,getServerVariable:r}=e;if(this.props.currentServer!==e.currentServer||this.props.servers!==e.servers){var o;let a=i()(t).call(t,(t=>t.get("url")===e.currentServer)),s=i()(o=this.props.servers).call(o,(e=>e.get("url")===this.props.currentServer))||(0,c.OrderedMap)();if(!a)return this.setServer(t.first().get("url"));let u=s.get("variables")||(0,c.OrderedMap)(),p=(i()(u).call(u,(e=>e.get("default")))||(0,c.OrderedMap)()).get("default"),f=a.get("variables")||(0,c.OrderedMap)(),h=(i()(f).call(f,(e=>e.get("default")))||(0,c.OrderedMap)()).get("default");l()(f).call(f,((t,o)=>{r(e.currentServer,o)&&p===h||n({server:e.currentServer,key:o,val:t.get("default")||""})}))}}render(){var e,t;let{servers:n,currentServer:r,getServerVariable:o,getEffectiveServerValue:a}=this.props,s=(i()(n).call(n,(e=>e.get("url")===r))||(0,c.OrderedMap)()).get("variables")||(0,c.OrderedMap)(),p=0!==s.size;return u.createElement("div",{className:"servers"},u.createElement("label",{htmlFor:"servers"},u.createElement("select",{onChange:this.onServerChange,value:r},l()(e=n.valueSeq()).call(e,(e=>u.createElement("option",{value:e.get("url"),key:e.get("url")},e.get("url"),e.get("description")&&` - ${e.get("description")}`))).toArray())),p?u.createElement("div",null,u.createElement("div",{className:"computed-url"},"Computed URL:",u.createElement("code",null,a(r))),u.createElement("h4",null,"Server variables"),u.createElement("table",null,u.createElement("tbody",null,l()(t=s.entrySeq()).call(t,(e=>{var t;let[n,a]=e;return u.createElement("tr",{key:n},u.createElement("td",null,n),u.createElement("td",null,a.get("enum")?u.createElement("select",{"data-variable":n,onChange:this.onServerVariableValueChange},l()(t=a.get("enum")).call(t,(e=>u.createElement("option",{selected:e===o(r,n),key:e,value:e},e)))):u.createElement("input",{type:"text",value:o(r,n)||"",onChange:this.onServerVariableValueChange,"data-variable":n})))}))))):null)}}},7779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{OAS3ComponentWrapFactory:()=>c,isOAS3:()=>l,isSwagger2:()=>u});var r=n(23101),o=n.n(r),a=n(27043),i=n.n(a),s=n(67294);function l(e){const t=e.get("openapi");return"string"==typeof t&&(i()(t).call(t,"3.0.")&&t.length>4)}function u(e){const t=e.get("swagger");return"string"==typeof t&&i()(t).call(t,"2.0")}function c(e){return(t,n)=>r=>{if(n&&n.specSelectors&&n.specSelectors.specJson){return l(n.specSelectors.specJson())?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r)}return console.warn("OAS3 wrapper: couldn't get spec"),null}}},97451:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(92044),o=n(73723),a=n(91741),i=n(76467),s=n(37761),l=n(67002),u=n(5065),c=n(62109);function p(){return{components:i.default,wrapComponents:s.default,statePlugins:{spec:{wrapSelectors:r,selectors:a},auth:{wrapSelectors:o},oas3:{actions:l,reducers:c.default,selectors:u}}}}},62109:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(8712),o=n.n(r),a=n(86),i=n.n(a),s=n(24282),l=n.n(s),u=n(43393),c=n(67002);const p={[c.UPDATE_SELECTED_SERVER]:(e,t)=>{let{payload:{selectedServerUrl:n,namespace:r}}=t;const o=r?[r,"selectedServer"]:["selectedServer"];return e.setIn(o,n)},[c.UPDATE_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[a,s]=r;if(!u.Map.isMap(n))return e.setIn(["requestData",a,s,"bodyValue"],n);let l,c=e.getIn(["requestData",a,s,"bodyValue"])||(0,u.Map)();u.Map.isMap(c)||(c=(0,u.Map)());const[...p]=o()(n).call(n);return i()(p).call(p,(e=>{let t=n.getIn([e]);c.has(e)&&u.Map.isMap(t)||(l=c.setIn([e,"value"],t))})),e.setIn(["requestData",a,s,"bodyValue"],l)},[c.UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,a]=r;return e.setIn(["requestData",o,a,"retainBodyValue"],n)},[c.UPDATE_REQUEST_BODY_INCLUSION]:(e,t)=>{let{payload:{value:n,pathMethod:r,name:o}}=t,[a,i]=r;return e.setIn(["requestData",a,i,"bodyInclusion",o],n)},[c.UPDATE_ACTIVE_EXAMPLES_MEMBER]:(e,t)=>{let{payload:{name:n,pathMethod:r,contextType:o,contextName:a}}=t,[i,s]=r;return e.setIn(["examples",i,s,o,a,"activeExample"],n)},[c.UPDATE_REQUEST_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,a]=r;return e.setIn(["requestData",o,a,"requestContentType"],n)},[c.UPDATE_RESPONSE_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,path:r,method:o}}=t;return e.setIn(["requestData",r,o,"responseContentType"],n)},[c.UPDATE_SERVER_VARIABLE_VALUE]:(e,t)=>{let{payload:{server:n,namespace:r,key:o,val:a}}=t;const i=r?[r,"serverVariableValues",n,o]:["serverVariableValues",n,o];return e.setIn(i,a)},[c.SET_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r,validationErrors:o}}=t,a=[];if(a.push("Required field is not provided"),o.missingBodyValue)return e.setIn(["requestData",n,r,"errors"],(0,u.fromJS)(a));if(o.missingRequiredKeys&&o.missingRequiredKeys.length>0){const{missingRequiredKeys:t}=o;return e.updateIn(["requestData",n,r,"bodyValue"],(0,u.fromJS)({}),(e=>l()(t).call(t,((e,t)=>e.setIn([t,"errors"],(0,u.fromJS)(a))),e)))}return console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR"),e},[c.CLEAR_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r}}=t;const a=e.getIn(["requestData",n,r,"bodyValue"]);if(!u.Map.isMap(a))return e.setIn(["requestData",n,r,"errors"],(0,u.fromJS)([]));const[...i]=o()(a).call(a);return i?e.updateIn(["requestData",n,r,"bodyValue"],(0,u.fromJS)({}),(e=>l()(i).call(i,((e,t)=>e.setIn([t,"errors"],(0,u.fromJS)([]))),e))):e},[c.CLEAR_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{pathMethod:n}}=t,[r,o]=n;const a=e.getIn(["requestData",r,o,"bodyValue"]);return a?u.Map.isMap(a)?e.setIn(["requestData",r,o,"bodyValue"],(0,u.Map)()):e.setIn(["requestData",r,o,"bodyValue"],""):e}}},5065:(e,t,n)=>{"use strict";n.r(t),n.d(t,{activeExamplesMember:()=>_,hasUserEditedBody:()=>w,requestBodyErrors:()=>x,requestBodyInclusionSetting:()=>E,requestBodyValue:()=>y,requestContentType:()=>S,responseContentType:()=>A,selectDefaultRequestBodyValue:()=>b,selectedServer:()=>g,serverEffectiveValue:()=>O,serverVariableValue:()=>C,serverVariables:()=>k,shouldRetainRequestBodyValue:()=>v,validateBeforeExecute:()=>j,validateShallowRequired:()=>I});var r=n(97606),o=n.n(r),a=n(86),i=n.n(a),s=n(28222),l=n.n(s),u=n(11882),c=n.n(u),p=n(43393),f=n(7779),h=n(42458),d=n(90242);function m(e){return function(){for(var t=arguments.length,n=new Array(t),r=0;r{const r=t.getSystem().specSelectors.specJson();return(0,f.isOAS3)(r)?e(...n):null}}}const g=m(((e,t)=>{const n=t?[t,"selectedServer"]:["selectedServer"];return e.getIn(n)||""})),y=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyValue"])||null)),v=m(((e,t,n)=>e.getIn(["requestData",t,n,"retainBodyValue"])||!1)),b=(e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o}=e.getSystem(),a=o.specJson();if((0,f.isOAS3)(a)){const e=r.requestContentType(t,n);if(e)return(0,h.getDefaultRequestBodyValue)(o.specResolvedSubtree(["paths",t,n,"requestBody"]),e,r.activeExamplesMember(t,n,"requestBody","requestBody"))}return null},w=(e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o}=e.getSystem(),a=o.specJson();if((0,f.isOAS3)(a)){let e=!1;const a=r.requestContentType(t,n);let i=r.requestBodyValue(t,n);if(p.Map.isMap(i)&&(i=(0,d.Pz)(i.mapEntries((e=>p.Map.isMap(e[1])?[e[0],e[1].get("value")]:e)).toJS())),p.List.isList(i)&&(i=(0,d.Pz)(i)),a){const s=(0,h.getDefaultRequestBodyValue)(o.specResolvedSubtree(["paths",t,n,"requestBody"]),a,r.activeExamplesMember(t,n,"requestBody","requestBody"));e=!!i&&i!==s}return e}return null},E=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyInclusion"])||(0,p.Map)())),x=m(((e,t,n)=>e.getIn(["requestData",t,n,"errors"])||null)),_=m(((e,t,n,r,o)=>e.getIn(["examples",t,n,r,o,"activeExample"])||null)),S=m(((e,t,n)=>e.getIn(["requestData",t,n,"requestContentType"])||null)),A=m(((e,t,n)=>e.getIn(["requestData",t,n,"responseContentType"])||null)),C=m(((e,t,n)=>{let r;if("string"!=typeof t){const{server:e,namespace:o}=t;r=o?[o,"serverVariableValues",e,n]:["serverVariableValues",e,n]}else{r=["serverVariableValues",t,n]}return e.getIn(r)||null})),k=m(((e,t)=>{let n;if("string"!=typeof t){const{server:e,namespace:r}=t;n=r?[r,"serverVariableValues",e]:["serverVariableValues",e]}else{n=["serverVariableValues",t]}return e.getIn(n)||(0,p.OrderedMap)()})),O=m(((e,t)=>{var n,r;if("string"!=typeof t){const{server:o,namespace:a}=t;r=o,n=a?e.getIn([a,"serverVariableValues",r]):e.getIn(["serverVariableValues",r])}else r=t,n=e.getIn(["serverVariableValues",r]);n=n||(0,p.OrderedMap)();let a=r;return o()(n).call(n,((e,t)=>{a=a.replace(new RegExp(`{${t}}`,"g"),e)})),a})),j=(T=(e,t)=>((e,t)=>(t=t||[],!!e.getIn(["requestData",...t,"bodyValue"])))(e,t),function(){for(var e=arguments.length,t=new Array(e),n=0;n{const n=e.getSystem().specSelectors.specJson();let r=[...t][1]||[];return!n.getIn(["paths",...r,"requestBody","required"])||T(...t)}});var T;const I=(e,t)=>{var n;let{oas3RequiredRequestBodyContentType:r,oas3RequestContentType:o,oas3RequestBodyValue:a}=t,s=[];if(!p.Map.isMap(a))return s;let u=[];return i()(n=l()(r.requestContentType)).call(n,(e=>{if(e===o){let t=r.requestContentType[e];i()(t).call(t,(e=>{c()(u).call(u,e)<0&&u.push(e)}))}})),i()(u).call(u,(e=>{a.getIn([e,"value"])||s.push(e)})),s}},91741:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isSwagger2:()=>p,servers:()=>u});var r=n(20573),o=n(43393),a=n(7779);const i=e=>e||(0,o.Map)(),s=(0,r.P1)(i,(e=>e.get("json",(0,o.Map)()))),l=(0,r.P1)(i,(e=>e.get("resolved",(0,o.Map)()))),u=(c=(0,r.P1)((e=>{let t=l(e);return t.count()<1&&(t=s(e)),t}),(e=>e.getIn(["servers"])||(0,o.Map)())),()=>function(e){const t=e.getSystem().specSelectors.specJson();if((0,a.isOAS3)(t)){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o()=>{const e=t.getSystem().specSelectors.specJson();return(0,a.isSwagger2)(e)}},92044:(e,t,n)=>{"use strict";n.r(t),n.d(t,{basePath:()=>y,consumes:()=>v,definitions:()=>h,hasHost:()=>d,host:()=>g,isOAS3:()=>x,isSwagger2:()=>_,produces:()=>b,schemes:()=>w,securityDefinitions:()=>m,servers:()=>E});var r=n(20573),o=n(33881),a=n(43393),i=n(7779);function s(e){return(t,n)=>function(){const r=n.getSystem().specSelectors.specJson();return(0,i.isOAS3)(r)?e(...arguments):t(...arguments)}}const l=e=>e||(0,a.Map)(),u=s((0,r.P1)((()=>null))),c=(0,r.P1)(l,(e=>e.get("json",(0,a.Map)()))),p=(0,r.P1)(l,(e=>e.get("resolved",(0,a.Map)()))),f=e=>{let t=p(e);return t.count()<1&&(t=c(e)),t},h=s((0,r.P1)(f,(e=>{const t=e.getIn(["components","schemas"]);return a.Map.isMap(t)?t:(0,a.Map)()}))),d=s((e=>f(e).hasIn(["servers",0]))),m=s((0,r.P1)(o.specJsonWithResolvedSubtrees,(e=>e.getIn(["components","securitySchemes"])||null))),g=u,y=u,v=u,b=u,w=u,E=s((0,r.P1)(f,(e=>e.getIn(["servers"])||(0,a.Map)()))),x=(e,t)=>()=>{const e=t.getSystem().specSelectors.specJson();return(0,i.isOAS3)(a.Map.isMap(e)?e:(0,a.Map)())},_=(e,t)=>()=>{const e=t.getSystem().specSelectors.specJson();return(0,i.isSwagger2)(a.Map.isMap(e)?e:(0,a.Map)())}},70356:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:a,errSelectors:i,authorized:s,onAuthChange:l,name:u}=n,c=a("HttpAuth");return"http"===o.get("type")?r.createElement(c,{key:u,schema:o,name:u,errSelectors:i,authorized:s,getComponent:a,onChange:l}):r.createElement(t,n)}))},37761:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(22460),o=n(70356),a=n(69487),i=n(50058),s=n(53499),l=n(90287);const u={Markdown:r.default,AuthItem:o.default,JsonSchema_string:l.default,VersionStamp:a.default,model:s.default,onlineValidatorBadge:i.default}},90287:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:a,errors:i,onChange:s}=n,l=o&&o.get?o.get("format"):null,u=o&&o.get?o.get("type"):null,c=a("Input");return u&&"string"===u&&l&&("binary"===l||"base64"===l)?r.createElement(c,{type:"file",className:i.length?"invalid":"",title:i.length?i:"",onChange:e=>{s(e.target.files[0])},disabled:t.isDisabled}):r.createElement(t,n)}))},22460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{Markdown:()=>f,default:()=>h});var r=n(81607),o=n.n(r),a=n(67294),i=n(94184),s=n.n(i),l=n(89927),u=n(7779),c=n(94994);const p=new l._("commonmark");p.block.ruler.enable(["table"]),p.set({linkTarget:"_blank"});const f=e=>{let{source:t,className:n="",getConfigs:r}=e;if("string"!=typeof t)return null;if(t){const{useUnsafeMarkdown:e}=r(),i=p.render(t),l=(0,c.s)(i,{useUnsafeMarkdown:e});let u;return"string"==typeof l&&(u=o()(l).call(l)),a.createElement("div",{dangerouslySetInnerHTML:{__html:u},className:s()(n,"renderedMarkdown")})}return null};f.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const h=(0,u.OAS3ComponentWrapFactory)(f)},53499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(23101),o=n.n(r),a=n(67294),i=n(7779),s=n(53795);class l extends a.Component{render(){let{getConfigs:e,schema:t}=this.props,n=["model-box"],r=null;return!0===t.get("deprecated")&&(n.push("deprecated"),r=a.createElement("span",{className:"model-deprecated-warning"},"Deprecated:")),a.createElement("div",{className:n.join(" ")},r,a.createElement(s.Z,o()({},this.props,{getConfigs:e,depth:1,expandDepth:this.props.expandDepth||0})))}}const u=(0,i.OAS3ComponentWrapFactory)(l)},50058:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(7779),o=n(5623);const a=(0,r.OAS3ComponentWrapFactory)(o.Z)},69487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{const{Ori:t}=e;return r.createElement("span",null,r.createElement(t,e),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS3")))}))},28560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(87198),o=n.n(r);let a=!1;function i(){return{statePlugins:{spec:{wrapActions:{updateSpec:e=>function(){return a=!0,e(...arguments)},updateJsonSpec:(e,t)=>function(){const n=t.getConfigs().onComplete;return a&&"function"==typeof n&&(o()(n,0),a=!1),e(...arguments)}}}}}}},92135:(e,t,n)=>{"use strict";n.r(t),n.d(t,{requestSnippetGenerator_curl_bash:()=>A,requestSnippetGenerator_curl_cmd:()=>C,requestSnippetGenerator_curl_powershell:()=>S});var r=n(11882),o=n.n(r),a=n(81607),i=n.n(a),s=n(35627),l=n.n(s),u=n(97606),c=n.n(u),p=n(12196),f=n.n(p),h=n(74386),d=n.n(h),m=n(58118),g=n.n(m),y=n(27504),v=n(43393);const b=e=>{var t;const n="_**[]";return o()(e).call(e,n)<0?e:i()(t=e.split(n)[0]).call(t)},w=e=>"-d "===e||/^[_\/-]/g.test(e)?e:"'"+e.replace(/'/g,"'\\''")+"'",E=e=>"-d "===(e=e.replace(/\^/g,"^^").replace(/\\"/g,'\\\\"').replace(/"/g,'""').replace(/\n/g,"^\n"))?e.replace(/-d /g,"-d ^\n"):/^[_\/-]/g.test(e)?e:'"'+e+'"',x=e=>"-d "===e?e:/\n/.test(e)?'@"\n'+e.replace(/"/g,'\\"').replace(/`/g,"``").replace(/\$/,"`$")+'\n"@':/^[_\/-]/g.test(e)?e:"'"+e.replace(/"/g,'""').replace(/'/g,"''")+"'";const _=function(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",o=!1,a="";const i=function(){for(var e=arguments.length,n=new Array(e),r=0;ra+=` ${n}`,p=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return a+=f()(" ").call(" ",e)};let h=e.get("headers");if(a+="curl"+r,e.has("curlOptions")&&i(...e.get("curlOptions")),i("-X",e.get("method")),u(),p(),s(`${e.get("url")}`),h&&h.size)for(let t of d()(m=e.get("headers")).call(m)){var m;u(),p();let[e,n]=t;s("-H",`${e}: ${n}`),o=o||/^content-type$/i.test(e)&&/^multipart\/form-data$/i.test(n)}const w=e.get("body");var E;if(w)if(o&&g()(E=["POST","PUT","PATCH"]).call(E,e.get("method")))for(let[e,t]of w.entrySeq()){let n=b(e);u(),p(),s("-F"),t instanceof y.Z.File?i(`${n}=@${t.name}${t.type?`;type=${t.type}`:""}`):i(`${n}=${t}`)}else if(w instanceof y.Z.File)u(),p(),s(`--data-binary '@${w.name}'`);else{u(),p(),s("-d ");let t=w;v.Map.isMap(t)?s(function(e){let t=[];for(let[n,r]of e.get("body").entrySeq()){let e=b(n);r instanceof y.Z.File?t.push(` "${e}": {\n "name": "${r.name}"${r.type?`,\n "type": "${r.type}"`:""}\n }`):t.push(` "${e}": ${l()(r,null,2).replace(/(\r\n|\r|\n)/g,"\n ")}`)}return`{\n${t.join(",\n")}\n}`}(e)):("string"!=typeof t&&(t=l()(t)),s(t))}else w||"POST"!==e.get("method")||(u(),p(),s("-d ''"));return a},S=e=>_(e,x,"`\n",".exe"),A=e=>_(e,w,"\\\n"),C=e=>_(e,E,"^\n")},86575:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(92135),o=n(4669),a=n(84206);const i=()=>({components:{RequestSnippets:a.default},fn:r,statePlugins:{requestSnippets:{selectors:o}}})},84206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(14418),o=n.n(r),a=n(25110),i=n.n(a),s=n(86),l=n.n(s),u=n(97606),c=n.n(u),p=n(67294),f=n(27361),h=n.n(f),d=n(23560),m=n.n(d),g=n(74855),y=n(33424);const v={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(250, 250, 250)",paddingBottom:"0",paddingTop:"0",border:"1px solid rgb(51, 51, 51)",borderRadius:"4px 4px 0 0",boxShadow:"none",borderBottom:"none"},b={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(51, 51, 51)",boxShadow:"none",border:"1px solid rgb(51, 51, 51)",paddingBottom:"0",paddingTop:"0",borderRadius:"4px 4px 0 0",marginTop:"-5px",marginRight:"-5px",marginLeft:"-5px",zIndex:"9999",borderBottom:"none"},w=e=>{var t,n;let{request:r,requestSnippetsSelectors:a,getConfigs:s}=e;const u=m()(s)?s():null,f=!1!==h()(u,"syntaxHighlight")&&h()(u,"syntaxHighlight.activated",!0),d=(0,p.useRef)(null),[w,E]=(0,p.useState)(null===(t=a.getSnippetGenerators())||void 0===t?void 0:t.keySeq().first()),[x,_]=(0,p.useState)(null==a?void 0:a.getDefaultExpanded());(0,p.useEffect)((()=>{}),[]),(0,p.useEffect)((()=>{var e;const t=o()(e=i()(d.current.childNodes)).call(e,(e=>{var t;return!!e.nodeType&&(null===(t=e.classList)||void 0===t?void 0:t.contains("curl-command"))}));return l()(t).call(t,(e=>e.addEventListener("mousewheel",j,{passive:!1}))),()=>{l()(t).call(t,(e=>e.removeEventListener("mousewheel",j)))}}),[r]);const S=a.getSnippetGenerators(),A=S.get(w),C=A.get("fn")(r),k=()=>{_(!x)},O=e=>e===w?b:v,j=e=>{const{target:t,deltaY:n}=e,{scrollHeight:r,offsetHeight:o,scrollTop:a}=t;r>o&&(0===a&&n<0||o+a>=r&&n>0)&&e.preventDefault()},T=f?p.createElement(y.d3,{language:A.get("syntax"),className:"curl microlight",style:(0,y.C2)(h()(u,"syntaxHighlight.theme"))},C):p.createElement("textarea",{readOnly:!0,className:"curl",value:C});return p.createElement("div",{className:"request-snippets",ref:d},p.createElement("div",{style:{width:"100%",display:"flex",justifyContent:"flex-start",alignItems:"center",marginBottom:"15px"}},p.createElement("h4",{onClick:()=>k(),style:{cursor:"pointer"}},"Snippets"),p.createElement("button",{onClick:()=>k(),style:{border:"none",background:"none"},title:x?"Collapse operation":"Expand operation"},p.createElement("svg",{className:"arrow",width:"10",height:"10"},p.createElement("use",{href:x?"#large-arrow-down":"#large-arrow",xlinkHref:x?"#large-arrow-down":"#large-arrow"})))),x&&p.createElement("div",{className:"curl-command"},p.createElement("div",{style:{paddingLeft:"15px",paddingRight:"10px",width:"100%",display:"flex"}},c()(n=S.entrySeq()).call(n,(e=>{let[t,n]=e;return p.createElement("div",{style:O(t),className:"btn",key:t,onClick:()=>(e=>{w!==e&&E(e)})(t)},p.createElement("h4",{style:t===w?{color:"white"}:{}},n.get("title")))}))),p.createElement("div",{className:"copy-to-clipboard"},p.createElement(g.CopyToClipboard,{text:C},p.createElement("button",null))),p.createElement("div",null,T)))}},4669:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getActiveLanguage:()=>d,getDefaultExpanded:()=>m,getGenerators:()=>f,getSnippetGenerators:()=>h});var r=n(14418),o=n.n(r),a=n(58118),i=n.n(a),s=n(97606),l=n.n(s),u=n(20573),c=n(43393);const p=e=>e||(0,c.Map)(),f=(0,u.P1)(p,(e=>{const t=e.get("languages"),n=e.get("generators",(0,c.Map)());return!t||t.isEmpty()?n:o()(n).call(n,((e,n)=>i()(t).call(t,n)))})),h=e=>t=>{var n,r;let{fn:a}=t;return o()(n=l()(r=f(e)).call(r,((e,t)=>{const n=(e=>a[`requestSnippetGenerator_${e}`])(t);return"function"!=typeof n?null:e.set("fn",n)}))).call(n,(e=>e))},d=(0,u.P1)(p,(e=>e.get("activeLanguage"))),m=(0,u.P1)(p,(e=>e.get("defaultExpanded")))},36195:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ErrorBoundary:()=>i,default:()=>s});var r=n(67294),o=n(56189),a=n(29403);class i extends r.Component{static getDerivedStateFromError(e){return{hasError:!0,error:e}}constructor(){super(...arguments),this.state={hasError:!1,error:null}}componentDidCatch(e,t){this.props.fn.componentDidCatch(e,t)}render(){const{getComponent:e,targetName:t,children:n}=this.props;if(this.state.hasError){const n=e("Fallback");return r.createElement(n,{name:t})}return n}}i.defaultProps={targetName:"this component",getComponent:()=>a.default,fn:{componentDidCatch:o.componentDidCatch},children:null};const s=i},29403:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{name:t}=e;return r.createElement("div",{className:"fallback"},"😱 ",r.createElement("i",null,"Could not render ","t"===t?"this component":t,", see the console."))}},56189:(e,t,n)=>{"use strict";n.r(t),n.d(t,{componentDidCatch:()=>i,withErrorBoundary:()=>s});var r=n(23101),o=n.n(r),a=n(67294);const i=console.error,s=e=>t=>{const{getComponent:n,fn:r}=e(),i=n("ErrorBoundary"),s=r.getDisplayName(t);class l extends a.Component{render(){return a.createElement(i,{targetName:s,getComponent:n,fn:r},a.createElement(t,o()({},this.props,this.context)))}}var u;return l.displayName=`WithErrorBoundary(${s})`,(u=t).prototype&&u.prototype.isReactComponent&&(l.prototype.mapStateToProps=t.prototype.mapStateToProps),l}},27621:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(47475),o=n.n(r),a=n(7287),i=n.n(a),s=n(36195),l=n(29403),u=n(56189);const c=function(){let{componentList:e=[],fullOverride:t=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=>{var r;let{getSystem:a}=n;const c=t?e:["App","BaseLayout","VersionPragmaFilter","InfoContainer","ServersContainer","SchemesContainer","AuthorizeBtnContainer","FilterContainer","Operations","OperationContainer","parameters","responses","OperationServers","Models","ModelWrapper",...e],p=i()(c,o()(r=Array(c.length)).call(r,((e,t)=>{let{fn:n}=t;return n.withErrorBoundary(e)})));return{fn:{componentDidCatch:u.componentDidCatch,withErrorBoundary:(0,u.withErrorBoundary)(a)},components:{ErrorBoundary:s.default,Fallback:l.default},wrapComponents:p}}}},57050:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>z,inferSchema:()=>U,memoizedCreateXMLExample:()=>V,memoizedSampleFromSchema:()=>W,sampleFromSchema:()=>q,sampleFromSchemaGeneric:()=>F});var r=n(11882),o=n.n(r),a=n(86),i=n.n(a),s=n(58309),l=n.n(s),u=n(58118),c=n.n(u),p=n(92039),f=n.n(p),h=n(24278),d=n.n(h),m=n(51679),g=n.n(m),y=n(39022),v=n.n(y),b=n(97606),w=n.n(b),E=n(35627),x=n.n(E),_=n(53479),S=n.n(_),A=n(14419),C=n.n(A),k=n(41609),O=n.n(k),j=n(90242),T=n(60314);const I={string:e=>e.pattern?(e=>{try{return new(C())(e).gen()}catch(e){return"string"}})(e.pattern):"string",string_email:()=>"user@example.com","string_date-time":()=>(new Date).toISOString(),string_date:()=>(new Date).toISOString().substring(0,10),string_uuid:()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6",string_hostname:()=>"example.com",string_ipv4:()=>"198.51.100.42",string_ipv6:()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a",number:()=>0,number_float:()=>0,integer:()=>0,boolean:e=>"boolean"!=typeof e.default||e.default},N=e=>{e=(0,j.mz)(e);let{type:t,format:n}=e,r=I[`${t}_${n}`]||I[t];return(0,j.Wl)(r)?r(e):"Unknown Type: "+e.type},P=e=>(0,j.XV)(e,"$$ref",(e=>"string"==typeof e&&o()(e).call(e,"#")>-1)),R=["maxProperties","minProperties"],M=["minItems","maxItems"],D=["minimum","maximum","exclusiveMinimum","exclusiveMaximum"],L=["minLength","maxLength"],B=function(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var a;(i()(n=["example","default","enum","xml","type",...R,...M,...D,...L]).call(n,(n=>(n=>{void 0===t[n]&&void 0!==e[n]&&(t[n]=e[n])})(n))),void 0!==e.required&&l()(e.required))&&(void 0!==t.required&&t.required.length||(t.required=[]),i()(a=e.required).call(a,(e=>{var n;c()(n=t.required).call(n,e)||t.required.push(e)})));if(e.properties){t.properties||(t.properties={});let n=(0,j.mz)(e.properties);for(let a in n){var s;if(Object.prototype.hasOwnProperty.call(n,a))if(!n[a]||!n[a].deprecated)if(!n[a]||!n[a].readOnly||r.includeReadOnly)if(!n[a]||!n[a].writeOnly||r.includeWriteOnly)if(!t.properties[a])t.properties[a]=n[a],!e.required&&l()(e.required)&&-1!==o()(s=e.required).call(s,a)&&(t.required?t.required.push(a):t.required=[a])}}return e.items&&(t.items||(t.items={}),t.items=B(e.items,t.items,r)),t},F=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];e&&(0,j.Wl)(e.toJS)&&(e=e.toJS());let a=void 0!==n||e&&void 0!==e.example||e&&void 0!==e.default;const s=!a&&e&&e.oneOf&&e.oneOf.length>0,u=!a&&e&&e.anyOf&&e.anyOf.length>0;if(!a&&(s||u)){const n=(0,j.mz)(s?e.oneOf[0]:e.anyOf[0]);if(B(n,e,t),!e.xml&&n.xml&&(e.xml=n.xml),void 0!==e.example&&void 0!==n.example)a=!0;else if(n.properties){e.properties||(e.properties={});let r=(0,j.mz)(n.properties);for(let a in r){var p;if(Object.prototype.hasOwnProperty.call(r,a))if(!r[a]||!r[a].deprecated)if(!r[a]||!r[a].readOnly||t.includeReadOnly)if(!r[a]||!r[a].writeOnly||t.includeWriteOnly)if(!e.properties[a])e.properties[a]=r[a],!n.required&&l()(n.required)&&-1!==o()(p=n.required).call(p,a)&&(e.required?e.required.push(a):e.required=[a])}}}const h={};let{xml:m,type:y,example:b,properties:E,additionalProperties:x,items:_}=e||{},{includeReadOnly:S,includeWriteOnly:A}=t;m=m||{};let C,{name:k,prefix:T,namespace:I}=m,L={};if(r&&(k=k||"notagname",C=(T?T+":":"")+k,I)){h[T?"xmlns:"+T:"xmlns"]=I}r&&(L[C]=[]);const U=t=>f()(t).call(t,(t=>Object.prototype.hasOwnProperty.call(e,t)));e&&!y&&(E||x||U(R)?y="object":_||U(M)?y="array":U(D)?(y="number",e.type="number"):a||e.enum||(y="string",e.type="string"));const z=t=>{var n,r,o,a,i;null!==(null===(n=e)||void 0===n?void 0:n.maxItems)&&void 0!==(null===(r=e)||void 0===r?void 0:r.maxItems)&&(t=d()(t).call(t,0,null===(i=e)||void 0===i?void 0:i.maxItems));if(null!==(null===(o=e)||void 0===o?void 0:o.minItems)&&void 0!==(null===(a=e)||void 0===a?void 0:a.minItems)){let n=0;for(;t.length<(null===(s=e)||void 0===s?void 0:s.minItems);){var s;t.push(t[n++%t.length])}}return t},q=(0,j.mz)(E);let $,V=0;const W=()=>e&&null!==e.maxProperties&&void 0!==e.maxProperties&&V>=e.maxProperties,H=t=>!e||null===e.maxProperties||void 0===e.maxProperties||!W()&&(!(t=>{var n;return!(e&&e.required&&e.required.length&&c()(n=e.required).call(n,t))})(t)||e.maxProperties-V-(()=>{if(!e||!e.required)return 0;let t=0;var n,o;return r?i()(n=e.required).call(n,(e=>t+=void 0===L[e]?0:1)):i()(o=e.required).call(o,(e=>{var n;return t+=void 0===(null===(n=L[C])||void 0===n?void 0:g()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if($=r?function(n){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&q[n]){if(q[n].xml=q[n].xml||{},q[n].xml.attribute){const e=l()(q[n].enum)?q[n].enum[0]:void 0,t=q[n].example,r=q[n].default;return void(h[q[n].xml.name||n]=void 0!==t?t:void 0!==r?r:void 0!==e?e:N(q[n]))}q[n].xml.name=q[n].xml.name||n}else q[n]||!1===x||(q[n]={xml:{name:n}});let a=F(e&&q[n]||void 0,t,o,r);var i;H(n)&&(V++,l()(a)?L[C]=v()(i=L[C]).call(i,a):L[C].push(a))}:(n,o)=>{if(H(n)){if(Object.prototype.hasOwnProperty.call(e,"discriminator")&&e.discriminator&&Object.prototype.hasOwnProperty.call(e.discriminator,"mapping")&&e.discriminator.mapping&&Object.prototype.hasOwnProperty.call(e,"$$ref")&&e.$$ref&&e.discriminator.propertyName===n){for(let t in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[t])){L[n]=t;break}}else L[n]=F(q[n],t,o,r);V++}},a){let o;if(o=P(void 0!==n?n:void 0!==b?b:e.default),!r){if("number"==typeof o&&"string"===y)return`${o}`;if("string"!=typeof o||"string"===y)return o;try{return JSON.parse(o)}catch(e){return o}}if(e||(y=l()(o)?"array":typeof o),"array"===y){if(!l()(o)){if("string"==typeof o)return o;o=[o]}const n=e?e.items:void 0;n&&(n.xml=n.xml||m||{},n.xml.name=n.xml.name||m.name);let a=w()(o).call(o,(e=>F(n,t,e,r)));return a=z(a),m.wrapped?(L[C]=a,O()(h)||L[C].push({_attr:h})):L=a,L}if("object"===y){if("string"==typeof o)return o;for(let t in o)Object.prototype.hasOwnProperty.call(o,t)&&(e&&q[t]&&q[t].readOnly&&!S||e&&q[t]&&q[t].writeOnly&&!A||(e&&q[t]&&q[t].xml&&q[t].xml.attribute?h[q[t].xml.name||t]=o[t]:$(t,o[t])));return O()(h)||L[C].push({_attr:h}),L}return L[C]=O()(h)?o:[{_attr:h},o],L}if("object"===y){for(let e in q)Object.prototype.hasOwnProperty.call(q,e)&&(q[e]&&q[e].deprecated||q[e]&&q[e].readOnly&&!S||q[e]&&q[e].writeOnly&&!A||$(e));if(r&&h&&L[C].push({_attr:h}),W())return L;if(!0===x)r?L[C].push({additionalProp:"Anything can be here"}):L.additionalProp1={},V++;else if(x){const n=(0,j.mz)(x),o=F(n,t,void 0,r);if(r&&n.xml&&n.xml.name&&"notagname"!==n.xml.name)L[C].push(o);else{const t=null!==e.minProperties&&void 0!==e.minProperties&&VF(B(_,e,t),t,void 0,r)));else if(l()(_.oneOf)){var G;n=w()(G=_.oneOf).call(G,(e=>F(B(_,e,t),t,void 0,r)))}else{if(!(!r||r&&m.wrapped))return F(_,t,void 0,r);n=[F(_,t,void 0,r)]}return n=z(n),r&&m.wrapped?(L[C]=n,O()(h)||L[C].push({_attr:h}),L):n}let Z;if(e&&l()(e.enum))Z=(0,j.AF)(e.enum)[0];else{if(!e)return;if(Z=N(e),"number"==typeof Z){let t=e.minimum;null!=t&&(e.exclusiveMinimum&&t++,Z=t);let n=e.maximum;null!=n&&(e.exclusiveMaximum&&n--,Z=n)}if("string"==typeof Z&&(null!==e.maxLength&&void 0!==e.maxLength&&(Z=d()(Z).call(Z,0,e.maxLength)),null!==e.minLength&&void 0!==e.minLength)){let t=0;for(;Z.length(e.schema&&(e=e.schema),e.properties&&(e.type="object"),e),z=(e,t,n)=>{const r=F(e,t,n,!0);if(r)return"string"==typeof r?r:S()(r,{declaration:!0,indent:"\t"})},q=(e,t,n)=>F(e,t,n,!1),$=(e,t,n)=>[e,x()(t),x()(n)],V=(0,T.Z)(z,$),W=(0,T.Z)(q,$)},8883:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(57050);function o(){return{fn:r}}},51228:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST:()=>Q,CLEAR_RESPONSE:()=>Y,CLEAR_VALIDATE_PARAMS:()=>X,LOG_REQUEST:()=>Z,SET_MUTATED_REQUEST:()=>G,SET_REQUEST:()=>K,SET_RESPONSE:()=>J,SET_SCHEME:()=>re,UPDATE_EMPTY_PARAM_INCLUSION:()=>W,UPDATE_JSON:()=>$,UPDATE_OPERATION_META_VALUE:()=>ee,UPDATE_PARAM:()=>V,UPDATE_RESOLVED:()=>te,UPDATE_RESOLVED_SUBTREE:()=>ne,UPDATE_SPEC:()=>z,UPDATE_URL:()=>q,VALIDATE_PARAMS:()=>H,changeConsumesValue:()=>xe,changeParam:()=>me,changeParamByIdentity:()=>ge,changeProducesValue:()=>_e,clearRequest:()=>Ie,clearResponse:()=>Te,clearValidateParams:()=>Ee,execute:()=>je,executeRequest:()=>Oe,invalidateResolvedSubtreeCache:()=>ve,logRequest:()=>ke,parseToJson:()=>ue,requestResolvedSubtree:()=>de,resolveSpec:()=>pe,setMutatedRequest:()=>Ce,setRequest:()=>Ae,setResponse:()=>Se,setScheme:()=>Ne,updateEmptyParamInclusion:()=>we,updateJsonSpec:()=>le,updateResolved:()=>ie,updateResolvedSubtree:()=>ye,updateSpec:()=>ae,updateUrl:()=>se,validateParams:()=>be});var r=n(58309),o=n.n(r),a=n(97606),i=n.n(a),s=n(96718),l=n.n(s),u=n(24282),c=n.n(u),p=n(2250),f=n.n(p),h=n(6226),d=n.n(h),m=n(14418),g=n.n(m),y=n(3665),v=n.n(y),b=n(11882),w=n.n(b),E=n(86),x=n.n(E),_=n(28222),S=n.n(_),A=n(76986),C=n.n(A),k=n(70586),O=n.n(k),j=n(1272),T=n(43393),I=n(84564),N=n.n(I),P=n(7710),R=n(47037),M=n.n(R),D=n(23279),L=n.n(D),B=n(36968),F=n.n(B),U=n(90242);const z="spec_update_spec",q="spec_update_url",$="spec_update_json",V="spec_update_param",W="spec_update_empty_param_inclusion",H="spec_validate_param",J="spec_set_response",K="spec_set_request",G="spec_set_mutated_request",Z="spec_log_request",Y="spec_clear_response",Q="spec_clear_request",X="spec_clear_validate_param",ee="spec_update_operation_meta_value",te="spec_update_resolved",ne="spec_update_resolved_subtree",re="set_scheme",oe=e=>M()(e)?e:"";function ae(e){const t=oe(e).replace(/\t/g," ");if("string"==typeof e)return{type:z,payload:t}}function ie(e){return{type:te,payload:e}}function se(e){return{type:q,payload:e}}function le(e){return{type:$,payload:e}}const ue=e=>t=>{let{specActions:n,specSelectors:r,errActions:o}=t,{specStr:a}=r,i=null;try{e=e||a(),o.clear({source:"parser"}),i=j.ZP.load(e,{schema:j.A8})}catch(e){return console.error(e),o.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return i&&"object"==typeof i?n.updateJsonSpec(i):{}};let ce=!1;const pe=(e,t)=>n=>{let{specActions:r,specSelectors:a,errActions:s,fn:{fetch:u,resolve:c,AST:p={}},getConfigs:f}=n;ce||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),ce=!0);const{modelPropertyMacro:h,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}=f();void 0===e&&(e=a.specJson()),void 0===t&&(t=a.url());let y=p.getLineNumberForPath?p.getLineNumberForPath:()=>{},v=a.specStr();return c({fetch:u,spec:e,baseDoc:t,modelPropertyMacro:h,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}).then((e=>{let{spec:t,errors:n}=e;if(s.clear({type:"thrown"}),o()(n)&&n.length>0){let e=i()(n).call(n,(e=>(console.error(e),e.line=e.fullPath?y(v,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));s.newThrownErrBatch(e)}return r.updateResolved(t)}))};let fe=[];const he=L()((async()=>{const e=fe.system;if(!e)return void console.error("debResolveSubtrees: don't have a system to operate on, aborting.");const{errActions:t,errSelectors:n,fn:{resolveSubtree:r,fetch:a,AST:s={}},specSelectors:u,specActions:p}=e;if(!r)return void console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.");let h=s.getLineNumberForPath?s.getLineNumberForPath:()=>{};const m=u.specStr(),{modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E}=e.getConfigs();try{var x=await c()(fe).call(fe,(async(e,s)=>{const{resultMap:c,specWithCurrentSubtrees:p}=await e,{errors:x,spec:_}=await r(p,s,{baseDoc:u.url(),modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E});if(n.allErrors().size&&t.clearBy((e=>{var t;return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!f()(t=e.get("fullPath")).call(t,((e,t)=>e===s[t]||void 0===s[t]))})),o()(x)&&x.length>0){let e=i()(x).call(x,(e=>(e.line=e.fullPath?h(m,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));t.newThrownErrBatch(e)}var S,A;_&&u.isOAS3()&&"components"===s[0]&&"securitySchemes"===s[1]&&await d().all(i()(S=g()(A=v()(_)).call(A,(e=>"openIdConnect"===e.type))).call(S,(async e=>{const t={url:e.openIdConnectUrl,requestInterceptor:w,responseInterceptor:E};try{const n=await a(t);n instanceof Error||n.status>=400?console.error(n.statusText+" "+t.url):e.openIdConnectData=JSON.parse(n.text)}catch(e){console.error(e)}})));return F()(c,s,_),F()(p,s,_),{resultMap:c,specWithCurrentSubtrees:p}}),d().resolve({resultMap:(u.specResolvedSubtree([])||(0,T.Map)()).toJS(),specWithCurrentSubtrees:u.specJson().toJS()}));delete fe.system,fe=[]}catch(e){console.error(e)}p.updateResolvedSubtree([],x.resultMap)}),35),de=e=>t=>{var n;w()(n=i()(fe).call(fe,(e=>e.join("@@")))).call(n,e.join("@@"))>-1||(fe.push(e),fe.system=t,he())};function me(e,t,n,r,o){return{type:V,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}}function ge(e,t,n,r){return{type:V,payload:{path:e,param:t,value:n,isXml:r}}}const ye=(e,t)=>({type:ne,payload:{path:e,value:t}}),ve=()=>({type:ne,payload:{path:[],value:(0,T.Map)()}}),be=(e,t)=>({type:H,payload:{pathMethod:e,isOAS3:t}}),we=(e,t,n,r)=>({type:W,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}});function Ee(e){return{type:X,payload:{pathMethod:e}}}function xe(e,t){return{type:ee,payload:{path:e,value:t,key:"consumes_value"}}}function _e(e,t){return{type:ee,payload:{path:e,value:t,key:"produces_value"}}}const Se=(e,t,n)=>({payload:{path:e,method:t,res:n},type:J}),Ae=(e,t,n)=>({payload:{path:e,method:t,req:n},type:K}),Ce=(e,t,n)=>({payload:{path:e,method:t,req:n},type:G}),ke=e=>({payload:e,type:Z}),Oe=e=>t=>{let{fn:n,specActions:r,specSelectors:a,getConfigs:s,oas3Selectors:l}=t,{pathName:u,method:c,operation:p}=e,{requestInterceptor:f,responseInterceptor:h}=s(),d=p.toJS();var m,y;p&&p.get("parameters")&&x()(m=g()(y=p.get("parameters")).call(y,(e=>e&&!0===e.get("allowEmptyValue")))).call(m,(t=>{if(a.parameterInclusionSettingFor([u,c],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};const n=(0,U.cz)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}));if(e.contextUrl=N()(a.url()).toString(),d&&d.operationId?e.operationId=d.operationId:d&&u&&c&&(e.operationId=n.opId(d,u,c)),a.isOAS3()){const t=`${u}:${c}`;e.server=l.selectedServer(t)||l.selectedServer();const n=l.serverVariables({server:e.server,namespace:t}).toJS(),r=l.serverVariables({server:e.server}).toJS();e.serverVariables=S()(n).length?n:r,e.requestContentType=l.requestContentType(u,c),e.responseContentType=l.responseContentType(u,c)||"*/*";const a=l.requestBodyValue(u,c),s=l.requestBodyInclusionSetting(u,c);var v;if(a&&a.toJS)e.requestBody=g()(v=i()(a).call(a,(e=>T.Map.isMap(e)?e.get("value"):e))).call(v,((e,t)=>(o()(e)?0!==e.length:!(0,U.O2)(e))||s.get(t))).toJS();else e.requestBody=a}let b=C()({},e);b=n.buildRequest(b),r.setRequest(e.pathName,e.method,b);e.requestInterceptor=async t=>{let n=await f.apply(void 0,[t]),o=C()({},n);return r.setMutatedRequest(e.pathName,e.method,o),n},e.responseInterceptor=h;const w=O()();return n.execute(e).then((t=>{t.duration=O()()-w,r.setResponse(e.pathName,e.method,t)})).catch((t=>{"Failed to fetch"===t.message&&(t.name="",t.message='**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be "http" or "https" for CORS request.'),r.setResponse(e.pathName,e.method,{error:!0,err:(0,P.serializeError)(t)})}))},je=function(){let{path:e,method:t,...n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return r=>{let{fn:{fetch:o},specSelectors:a,specActions:i}=r,s=a.specJsonWithResolvedSubtrees().toJS(),l=a.operationScheme(e,t),{requestContentType:u,responseContentType:c}=a.contentTypeValues([e,t]).toJS(),p=/xml/i.test(u),f=a.parameterValues([e,t],p).toJS();return i.executeRequest({...n,fetch:o,spec:s,pathName:e,method:t,parameters:f,requestContentType:u,scheme:l,responseContentType:c})}};function Te(e,t){return{type:Y,payload:{path:e,method:t}}}function Ie(e,t){return{type:Q,payload:{path:e,method:t}}}function Ne(e,t,n){return{type:re,payload:{scheme:e,path:t,method:n}}}},37038:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(20032),o=n(51228),a=n(33881),i=n(77508);function s(){return{statePlugins:{spec:{wrapActions:i,reducers:r.default,actions:o,selectors:a}}}}},20032:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(24282),o=n.n(r),a=n(97606),i=n.n(a),s=n(76986),l=n.n(s),u=n(43393),c=n(90242),p=n(27504),f=n(33881),h=n(51228);const d={[h.UPDATE_SPEC]:(e,t)=>"string"==typeof t.payload?e.set("spec",t.payload):e,[h.UPDATE_URL]:(e,t)=>e.set("url",t.payload+""),[h.UPDATE_JSON]:(e,t)=>e.set("json",(0,c.oG)(t.payload)),[h.UPDATE_RESOLVED]:(e,t)=>e.setIn(["resolved"],(0,c.oG)(t.payload)),[h.UPDATE_RESOLVED_SUBTREE]:(e,t)=>{const{value:n,path:r}=t.payload;return e.setIn(["resolvedSubtrees",...r],(0,c.oG)(n))},[h.UPDATE_PARAM]:(e,t)=>{let{payload:n}=t,{path:r,paramName:o,paramIn:a,param:i,value:s,isXml:l}=n,u=i?(0,c.V9)(i):`${a}.${o}`;const p=l?"value_xml":"value";return e.setIn(["meta","paths",...r,"parameters",u,p],s)},[h.UPDATE_EMPTY_PARAM_INCLUSION]:(e,t)=>{let{payload:n}=t,{pathMethod:r,paramName:o,paramIn:a,includeEmptyValue:i}=n;if(!o||!a)return console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey."),e;const s=`${a}.${o}`;return e.setIn(["meta","paths",...r,"parameter_inclusions",s],i)},[h.VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n,isOAS3:r}}=t;const a=(0,f.specJsonWithResolvedSubtrees)(e).getIn(["paths",...n]),i=(0,f.parameterValues)(e,n).toJS();return e.updateIn(["meta","paths",...n,"parameters"],(0,u.fromJS)({}),(t=>{var s;return o()(s=a.get("parameters",(0,u.List)())).call(s,((t,o)=>{const a=(0,c.cz)(o,i),s=(0,f.parameterInclusionSettingFor)(e,n,o.get("name"),o.get("in")),l=(0,c.Ik)(o,a,{bypassRequiredCheck:s,isOAS3:r});return t.setIn([(0,c.V9)(o),"errors"],(0,u.fromJS)(l))}),t)}))},[h.CLEAR_VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n}}=t;return e.updateIn(["meta","paths",...n,"parameters"],(0,u.fromJS)([]),(e=>i()(e).call(e,(e=>e.set("errors",(0,u.fromJS)([]))))))},[h.SET_RESPONSE]:(e,t)=>{let n,{payload:{res:r,path:o,method:a}}=t;n=r.error?l()({error:!0,name:r.err.name,message:r.err.message,statusCode:r.err.statusCode},r.err.response):r,n.headers=n.headers||{};let i=e.setIn(["responses",o,a],(0,c.oG)(n));return p.Z.Blob&&r.data instanceof p.Z.Blob&&(i=i.setIn(["responses",o,a,"text"],r.data)),i},[h.SET_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["requests",r,o],(0,c.oG)(n))},[h.SET_MUTATED_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["mutatedRequests",r,o],(0,c.oG)(n))},[h.UPDATE_OPERATION_META_VALUE]:(e,t)=>{let{payload:{path:n,value:r,key:o}}=t,a=["paths",...n],i=["meta","paths",...n];return e.getIn(["json",...a])||e.getIn(["resolved",...a])||e.getIn(["resolvedSubtrees",...a])?e.setIn([...i,o],(0,u.fromJS)(r)):e},[h.CLEAR_RESPONSE]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["responses",n,r])},[h.CLEAR_REQUEST]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["requests",n,r])},[h.SET_SCHEME]:(e,t)=>{let{payload:{scheme:n,path:r,method:o}}=t;return r&&o?e.setIn(["scheme",r,o],n):r||o?void 0:e.setIn(["scheme","_defaultScheme"],n)}}},33881:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allowTryItOutFor:()=>pe,basePath:()=>Y,canExecuteScheme:()=>Ce,consumes:()=>W,consumesOptionsFor:()=>Se,contentTypeValues:()=>Ee,currentProducesFor:()=>xe,definitions:()=>Z,externalDocs:()=>U,findDefinition:()=>G,getOAS3RequiredRequestBodyContentType:()=>je,getParameter:()=>ge,hasHost:()=>ye,host:()=>Q,info:()=>F,isMediaTypeSchemaPropertiesEqual:()=>Te,isOAS3:()=>B,lastError:()=>O,mutatedRequestFor:()=>ce,mutatedRequests:()=>se,operationScheme:()=>Ae,operationWithMeta:()=>me,operations:()=>V,operationsWithRootInherited:()=>ee,operationsWithTags:()=>re,parameterInclusionSettingFor:()=>he,parameterValues:()=>ve,parameterWithMeta:()=>de,parameterWithMetaByIdentity:()=>fe,parametersIncludeIn:()=>be,parametersIncludeType:()=>we,paths:()=>$,produces:()=>H,producesOptionsFor:()=>_e,requestFor:()=>ue,requests:()=>ie,responseFor:()=>le,responses:()=>ae,schemes:()=>X,security:()=>J,securityDefinitions:()=>K,semver:()=>q,spec:()=>L,specJson:()=>N,specJsonWithResolvedSubtrees:()=>D,specResolved:()=>P,specResolvedSubtree:()=>R,specSource:()=>I,specStr:()=>T,tagDetails:()=>ne,taggedOperations:()=>oe,tags:()=>te,url:()=>j,validateBeforeExecute:()=>Oe,validationErrors:()=>ke,version:()=>z});var r=n(24278),o=n.n(r),a=n(86),i=n.n(a),s=n(11882),l=n.n(s),u=n(97606),c=n.n(u),p=n(14418),f=n.n(p),h=n(51679),d=n.n(h),m=n(24282),g=n.n(m),y=n(2578),v=n.n(y),b=n(92039),w=n.n(b),E=n(58309),x=n.n(E),_=n(20573),S=n(90242),A=n(43393);const C=["get","put","post","delete","options","head","patch","trace"],k=e=>e||(0,A.Map)(),O=(0,_.P1)(k,(e=>e.get("lastError"))),j=(0,_.P1)(k,(e=>e.get("url"))),T=(0,_.P1)(k,(e=>e.get("spec")||"")),I=(0,_.P1)(k,(e=>e.get("specSource")||"not-editor")),N=(0,_.P1)(k,(e=>e.get("json",(0,A.Map)()))),P=(0,_.P1)(k,(e=>e.get("resolved",(0,A.Map)()))),R=(e,t)=>e.getIn(["resolvedSubtrees",...t],void 0),M=(e,t)=>A.Map.isMap(e)&&A.Map.isMap(t)?t.get("$$ref")?t:(0,A.OrderedMap)().mergeWith(M,e,t):t,D=(0,_.P1)(k,(e=>(0,A.OrderedMap)().mergeWith(M,e.get("json"),e.get("resolvedSubtrees")))),L=e=>N(e),B=(0,_.P1)(L,(()=>!1)),F=(0,_.P1)(L,(e=>Ie(e&&e.get("info")))),U=(0,_.P1)(L,(e=>Ie(e&&e.get("externalDocs")))),z=(0,_.P1)(F,(e=>e&&e.get("version"))),q=(0,_.P1)(z,(e=>{var t;return o()(t=/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e)).call(t,1)})),$=(0,_.P1)(D,(e=>e.get("paths"))),V=(0,_.P1)($,(e=>{if(!e||e.size<1)return(0,A.List)();let t=(0,A.List)();return e&&i()(e)?(i()(e).call(e,((e,n)=>{if(!e||!i()(e))return{};i()(e).call(e,((e,r)=>{l()(C).call(C,r)<0||(t=t.push((0,A.fromJS)({path:n,method:r,operation:e,id:`${r}-${n}`})))}))})),t):(0,A.List)()})),W=(0,_.P1)(L,(e=>(0,A.Set)(e.get("consumes")))),H=(0,_.P1)(L,(e=>(0,A.Set)(e.get("produces")))),J=(0,_.P1)(L,(e=>e.get("security",(0,A.List)()))),K=(0,_.P1)(L,(e=>e.get("securityDefinitions"))),G=(e,t)=>{const n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},Z=(0,_.P1)(L,(e=>{const t=e.get("definitions");return A.Map.isMap(t)?t:(0,A.Map)()})),Y=(0,_.P1)(L,(e=>e.get("basePath"))),Q=(0,_.P1)(L,(e=>e.get("host"))),X=(0,_.P1)(L,(e=>e.get("schemes",(0,A.Map)()))),ee=(0,_.P1)(V,W,H,((e,t,n)=>c()(e).call(e,(e=>e.update("operation",(e=>{if(e){if(!A.Map.isMap(e))return;return e.withMutations((e=>(e.get("consumes")||e.update("consumes",(e=>(0,A.Set)(e).merge(t))),e.get("produces")||e.update("produces",(e=>(0,A.Set)(e).merge(n))),e)))}return(0,A.Map)()})))))),te=(0,_.P1)(L,(e=>{const t=e.get("tags",(0,A.List)());return A.List.isList(t)?f()(t).call(t,(e=>A.Map.isMap(e))):(0,A.List)()})),ne=(e,t)=>{var n;let r=te(e)||(0,A.List)();return d()(n=f()(r).call(r,A.Map.isMap)).call(n,(e=>e.get("name")===t),(0,A.Map)())},re=(0,_.P1)(ee,te,((e,t)=>g()(e).call(e,((e,t)=>{let n=(0,A.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,A.List)(),(e=>e.push(t))):g()(n).call(n,((e,n)=>e.update(n,(0,A.List)(),(e=>e.push(t)))),e)}),g()(t).call(t,((e,t)=>e.set(t.get("name"),(0,A.List)())),(0,A.OrderedMap)())))),oe=e=>t=>{var n;let{getConfigs:r}=t,{tagsSorter:o,operationsSorter:a}=r();return c()(n=re(e).sortBy(((e,t)=>t),((e,t)=>{let n="function"==typeof o?o:S.wh.tagsSorter[o];return n?n(e,t):null}))).call(n,((t,n)=>{let r="function"==typeof a?a:S.wh.operationsSorter[a],o=r?v()(t).call(t,r):t;return(0,A.Map)({tagDetails:ne(e,n),operations:o})}))},ae=(0,_.P1)(k,(e=>e.get("responses",(0,A.Map)()))),ie=(0,_.P1)(k,(e=>e.get("requests",(0,A.Map)()))),se=(0,_.P1)(k,(e=>e.get("mutatedRequests",(0,A.Map)()))),le=(e,t,n)=>ae(e).getIn([t,n],null),ue=(e,t,n)=>ie(e).getIn([t,n],null),ce=(e,t,n)=>se(e).getIn([t,n],null),pe=()=>!0,fe=(e,t,n)=>{const r=D(e).getIn(["paths",...t,"parameters"],(0,A.OrderedMap)()),o=e.getIn(["meta","paths",...t,"parameters"],(0,A.OrderedMap)()),a=c()(r).call(r,(e=>{const t=o.get(`${n.get("in")}.${n.get("name")}`),r=o.get(`${n.get("in")}.${n.get("name")}.hash-${n.hashCode()}`);return(0,A.OrderedMap)().merge(e,t,r)}));return d()(a).call(a,(e=>e.get("in")===n.get("in")&&e.get("name")===n.get("name")),(0,A.OrderedMap)())},he=(e,t,n,r)=>{const o=`${r}.${n}`;return e.getIn(["meta","paths",...t,"parameter_inclusions",o],!1)},de=(e,t,n,r)=>{const o=D(e).getIn(["paths",...t,"parameters"],(0,A.OrderedMap)()),a=d()(o).call(o,(e=>e.get("in")===r&&e.get("name")===n),(0,A.OrderedMap)());return fe(e,t,a)},me=(e,t,n)=>{var r;const o=D(e).getIn(["paths",t,n],(0,A.OrderedMap)()),a=e.getIn(["meta","paths",t,n],(0,A.OrderedMap)()),i=c()(r=o.get("parameters",(0,A.List)())).call(r,(r=>fe(e,[t,n],r)));return(0,A.OrderedMap)().merge(o,a).set("parameters",i)};function ge(e,t,n,r){t=t||[];let o=e.getIn(["meta","paths",...t,"parameters"],(0,A.fromJS)([]));return d()(o).call(o,(e=>A.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r))||(0,A.Map)()}const ye=(0,_.P1)(L,(e=>{const t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]}));function ve(e,t,n){t=t||[];let r=me(e,...t).get("parameters",(0,A.List)());return g()(r).call(r,((e,t)=>{let r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set((0,S.V9)(t,{allowHashes:!1}),r)}),(0,A.fromJS)({}))}function be(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(A.List.isList(e))return w()(e).call(e,(e=>A.Map.isMap(e)&&e.get("in")===t))}function we(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(A.List.isList(e))return w()(e).call(e,(e=>A.Map.isMap(e)&&e.get("type")===t))}function Ee(e,t){t=t||[];let n=D(e).getIn(["paths",...t],(0,A.fromJS)({})),r=e.getIn(["meta","paths",...t],(0,A.fromJS)({})),o=xe(e,t);const a=n.get("parameters")||new A.List,i=r.get("consumes_value")?r.get("consumes_value"):we(a,"file")?"multipart/form-data":we(a,"formData")?"application/x-www-form-urlencoded":void 0;return(0,A.fromJS)({requestContentType:i,responseContentType:o})}function xe(e,t){t=t||[];const n=D(e).getIn(["paths",...t],null);if(null===n)return;const r=e.getIn(["meta","paths",...t,"produces_value"],null),o=n.getIn(["produces",0],null);return r||o||"application/json"}function _e(e,t){t=t||[];const n=D(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,a=r.get("produces",null),i=n.getIn(["paths",o,"produces"],null),s=n.getIn(["produces"],null);return a||i||s}function Se(e,t){t=t||[];const n=D(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,a=r.get("consumes",null),i=n.getIn(["paths",o,"consumes"],null),s=n.getIn(["consumes"],null);return a||i||s}const Ae=(e,t,n)=>{let r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=x()(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""},Ce=(e,t,n)=>{var r;return l()(r=["http","https"]).call(r,Ae(e,t,n))>-1},ke=(e,t)=>{t=t||[];let n=e.getIn(["meta","paths",...t,"parameters"],(0,A.fromJS)([]));const r=[];return i()(n).call(n,(e=>{let t=e.get("errors");t&&t.count()&&i()(t).call(t,(e=>r.push(e)))})),r},Oe=(e,t)=>0===ke(e,t).length,je=(e,t)=>{var n;let r={requestBody:!1,requestContentType:{}},o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody"],(0,A.fromJS)([]));return o.size<1||(o.getIn(["required"])&&(r.requestBody=o.getIn(["required"])),i()(n=o.getIn(["content"]).entrySeq()).call(n,(e=>{const t=e[0];if(e[1].getIn(["schema","required"])){const n=e[1].getIn(["schema","required"]).toJS();r.requestContentType[t]=n}}))),r},Te=(e,t,n,r)=>{if((n||r)&&n===r)return!0;let o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody","content"],(0,A.fromJS)([]));if(o.size<2||!n||!r)return!1;let a=o.getIn([n,"schema","properties"],(0,A.fromJS)([])),i=o.getIn([r,"schema","properties"],(0,A.fromJS)([]));return!!a.equals(i)};function Ie(e){return A.Map.isMap(e)?e:new A.Map}},77508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{executeRequest:()=>p,updateJsonSpec:()=>c,updateSpec:()=>u,validateParams:()=>f});var r=n(28222),o=n.n(r),a=n(86),i=n.n(a),s=n(27361),l=n.n(s);const u=(e,t)=>{let{specActions:n}=t;return function(){e(...arguments),n.parseToJson(...arguments)}},c=(e,t)=>{let{specActions:n}=t;return function(){for(var t=arguments.length,r=new Array(t),a=0;a{l()(u,[e]).$ref&&n.requestResolvedSubtree(["paths",e])})),n.requestResolvedSubtree(["components","securitySchemes"])}},p=(e,t)=>{let{specActions:n}=t;return t=>(n.logRequest(t),e(t))},f=(e,t)=>{let{specSelectors:n}=t;return t=>e(t,n.isOAS3())}},34852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>function(){e(...arguments);const n=t.getConfigs().withCredentials;void 0!==n&&(t.fn.fetch.withCredentials="string"==typeof n?"true"===n:!!n)}},74370:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>on});var r={};n.r(r),n.d(r,{JsonPatchError:()=>G,_areEquals:()=>ae,applyOperation:()=>ee,applyPatch:()=>te,applyReducer:()=>ne,deepClone:()=>Z,getValueByPointer:()=>X,validate:()=>oe,validator:()=>re});var o={};n.r(o),n.d(o,{compare:()=>he,generate:()=>pe,observe:()=>ce,unobserve:()=>ue});var a={};n.r(a),n.d(a,{cookie:()=>Ft,header:()=>Bt,path:()=>Mt,query:()=>Dt});var i=n(58826),s=n.n(i);const l="application/json, application/yaml";function u(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{requestInterceptor:n,responseInterceptor:r}=t,o=e.withCredentials?"include":"same-origin";return t=>e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:l},credentials:o}).then((e=>e.body))}n(31905);var c=n(80129),p=n.n(c),f=n(1272);const h="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{FormData:d,Blob:m,File:g}=h,y=e=>":/?#[]@!$&'()*+,;=".indexOf(e)>-1,v=e=>/^[a-z0-9\-._~]+$/i.test(e);function b(e){let{escape:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&t?n?JSON.parse(e):[...e].map((e=>{if(v(e))return e;if(y(e)&&"unsafe"===t)return e;const n=new TextEncoder;return Array.from(n.encode(e)).map((e=>`0${e.toString(16).toUpperCase()}`.slice(-2))).map((e=>`%${e}`)).join("")})).join(""):e}function w(e){const{value:t}=e;return Array.isArray(t)?function(e){let{key:t,value:n,style:r,explode:o,escape:a}=e;const i=e=>b(e,{escape:a});if("simple"===r)return n.map((e=>i(e))).join(",");if("label"===r)return`.${n.map((e=>i(e))).join(".")}`;if("matrix"===r)return n.map((e=>i(e))).reduce(((e,n)=>!e||o?`${e||""};${t}=${n}`:`${e},${n}`),"");if("form"===r){const e=o?`&${t}=`:",";return n.map((e=>i(e))).join(e)}if("spaceDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(` ${e}`)}if("pipeDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(`|${e}`)}return}(e):"object"==typeof t?function(e){let{key:t,value:n,style:r,explode:o,escape:a}=e;const i=e=>b(e,{escape:a}),s=Object.keys(n);if("simple"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e},`:""}${t}${o?"=":","}${r}`}),"");if("label"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}.`:"."}${t}${o?"=":"."}${r}`}),"");if("matrix"===r&&o)return s.reduce(((e,t)=>`${e?`${e};`:";"}${t}=${i(n[t])}`),"");if("matrix"===r)return s.reduce(((e,r)=>{const o=i(n[r]);return`${e?`${e},`:`;${t}=`}${r},${o}`}),"");if("form"===r)return s.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}${o?"&":","}`:""}${t}${o?"=":","}${r}`}),"");return}(e):function(e){let{key:t,value:n,style:r,escape:o}=e;const a=e=>b(e,{escape:o});if("simple"===r)return a(n);if("label"===r)return`.${a(n)}`;if("matrix"===r)return`;${t}=${a(n)}`;if("form"===r)return a(n);if("deepObject"===r)return a(n,{},!0);return}(e)}const E=(e,t)=>{t.body=e},x={serializeRes:A,mergeInQueryOrForm:R};async function _(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"object"==typeof e&&(t=e,e=t.url),t.headers=t.headers||{},x.mergeInQueryOrForm(t),t.headers&&Object.keys(t.headers).forEach((e=>{const n=t.headers[e];"string"==typeof n&&(t.headers[e]=n.replace(/\n+/g," "))})),t.requestInterceptor&&(t=await t.requestInterceptor(t)||t);const n=t.headers["content-type"]||t.headers["Content-Type"];let r;/multipart\/form-data/i.test(n)&&t.body instanceof d&&(delete t.headers["content-type"],delete t.headers["Content-Type"]);try{r=await(t.userFetch||fetch)(t.url,t),r=await x.serializeRes(r,e,t),t.responseInterceptor&&(r=await t.responseInterceptor(r)||r)}catch(e){if(!r)throw e;const t=new Error(r.statusText||`response status is ${r.status}`);throw t.status=r.status,t.statusCode=r.status,t.responseError=e,t}if(!r.ok){const e=new Error(r.statusText||`response status is ${r.status}`);throw e.status=r.status,e.statusCode=r.status,e.response=r,e}return r}const S=function(){return/(json|xml|yaml|text)\b/.test(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")};function A(e,t){let{loadSpec:n=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:C(e.headers)},o=r.headers["content-type"],a=n||S(o);return(a?e.text:e.blob||e.buffer).call(e).then((e=>{if(r.text=e,r.data=e,a)try{const t=function(e,t){return t&&(0===t.indexOf("application/json")||t.indexOf("+json")>0)?JSON.parse(e):f.ZP.load(e)}(e,o);r.body=t,r.obj=t}catch(e){r.parseError=e}return r}))}function C(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return"function"!=typeof e.entries?{}:Array.from(e.entries()).reduce(((e,t)=>{let[n,r]=t;return e[n]=function(e){return e.includes(", ")?e.split(", "):e}(r),e}),{})}function k(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!=typeof e||"string"!=typeof e.uri):void 0!==g&&e instanceof g||(void 0!==m&&e instanceof m||(!!ArrayBuffer.isView(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe))}function O(e,t){return Array.isArray(e)&&e.some((e=>k(e,t)))}const j={form:",",spaceDelimited:"%20",pipeDelimited:"|"},T={csv:",",ssv:"%20",tsv:"%09",pipes:"|"};function I(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const{collectionFormat:r,allowEmptyValue:o,serializationOption:a,encoding:i}=t,s="object"!=typeof t||Array.isArray(t)?t:t.value,l=n?e=>e.toString():e=>encodeURIComponent(e),u=l(e);if(void 0===s&&o)return[[u,""]];if(k(s)||O(s))return[[u,s]];if(a)return N(e,s,n,a);if(i){if([typeof i.style,typeof i.explode,typeof i.allowReserved].some((e=>"undefined"!==e))){const{style:t,explode:r,allowReserved:o}=i;return N(e,s,n,{style:t,explode:r,allowReserved:o})}if(i.contentType){if("application/json"===i.contentType){return[[u,l("string"==typeof s?s:JSON.stringify(s))]]}return[[u,l(s.toString())]]}return"object"!=typeof s?[[u,l(s)]]:Array.isArray(s)&&s.every((e=>"object"!=typeof e))?[[u,s.map(l).join(",")]]:[[u,l(JSON.stringify(s))]]}return"object"!=typeof s?[[u,l(s)]]:Array.isArray(s)?"multi"===r?[[u,s.map(l)]]:[[u,s.map(l).join(T[r||"csv"])]]:[[u,""]]}function N(e,t,n,r){const o=r.style||"form",a=void 0===r.explode?"form"===o:r.explode,i=!n&&(r&&r.allowReserved?"unsafe":"reserved"),s=e=>b(e,{escape:i}),l=n?e=>e:e=>b(e,{escape:i});return"object"!=typeof t?[[l(e),s(t)]]:Array.isArray(t)?a?[[l(e),t.map(s)]]:[[l(e),t.map(s).join(j[o])]]:"deepObject"===o?Object.keys(t).map((n=>[l(`${e}[${n}]`),s(t[n])])):a?Object.keys(t).map((e=>[l(e),s(t[e])])):[[l(e),Object.keys(t).map((e=>[`${l(e)},${s(t[e])}`])).join(",")]]}function P(e){const t=Object.keys(e).reduce(((t,n)=>{for(const[r,o]of I(n,e[n]))t[r]=o;return t}),{});return p().stringify(t,{encode:!1,indices:!1})||""}function R(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{url:t="",query:n,form:r}=e;if(r){const t=Object.keys(r).some((e=>{const{value:t}=r[e];return k(t)||O(t)})),n=e.headers["content-type"]||e.headers["Content-Type"];if(t||/multipart\/form-data/i.test(n)){const t=(o=e.form,Object.entries(o).reduce(((e,t)=>{let[n,r]=t;for(const[t,o]of I(n,r,!0))if(Array.isArray(o))for(const n of o)if(ArrayBuffer.isView(n)){const r=new m([n]);e.append(t,r)}else e.append(t,n);else if(ArrayBuffer.isView(o)){const n=new m([o]);e.append(t,n)}else e.append(t,o);return e}),new d));E(t,e)}else e.body=P(r);delete e.form}var o;if(n){const[r,o]=t.split("?");let a="";if(o){const e=p().parse(o);Object.keys(n).forEach((t=>delete e[t])),a=p().stringify(e,{encode:!0})}const i=function(){for(var e=arguments.length,t=new Array(e),n=0;ne)).join("&");return r?`?${r}`:""}(a,P(n));e.url=r+i,delete e.query}return e}const M=e=>{const{baseDoc:t,url:n}=e;return t||n},D=e=>{const{fetch:t,http:n}=e;return t||n||_};var L,B=(L=function(e,t){return L=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},L(e,t)},function(e,t){function n(){this.constructor=e}L(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),F=Object.prototype.hasOwnProperty;function U(e,t){return F.call(e,t)}function z(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0}function V(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function W(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function H(e){if(void 0===e)return!0;if(e)if(Array.isArray(e)){for(var t=0,n=e.length;t0&&"constructor"==s[u-1]))throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===p&&(void 0===l[f]?p=s.slice(0,u).join("/"):u==c-1&&(p=t.path),void 0!==p&&h(t,0,e,p)),u++,Array.isArray(l)){if("-"===f)f=l.length;else{if(n&&!$(f))throw new G("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",a,t,e);$(f)&&(f=~~f)}if(u>=c){if(n&&"add"===t.op&&f>l.length)throw new G("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",a,t,e);if(!1===(i=Q[t.op].call(t,l,f,e)).test)throw new G("Test operation failed","TEST_OPERATION_FAILED",a,t,e);return i}}else if(u>=c){if(!1===(i=Y[t.op].call(t,l,f,e)).test)throw new G("Test operation failed","TEST_OPERATION_FAILED",a,t,e);return i}if(l=l[f],n&&u0)throw new G('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new G("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new G("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&H(e.value))throw new G("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",t,e,n);if(n)if("add"==e.op){var o=e.path.split("/").length,a=r.split("/").length;if(o!==a+1&&o!==a)throw new G("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",t,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==r)throw new G("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",t,e,n)}else if("move"===e.op||"copy"===e.op){var i=oe([{op:"_get",path:e.from,value:void 0}],n);if(i&&"OPERATION_PATH_UNRESOLVABLE"===i.name)throw new G("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",t,e,n)}}function oe(e,t,n){try{if(!Array.isArray(e))throw new G("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(t)te(q(t),q(e),n||!0);else{n=n||re;for(var r=0;r0&&(e.patches=[],e.callback&&e.callback(r)),r}function fe(e,t,n,r,o){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var a=z(t),i=z(e),s=!1,l=i.length-1;l>=0;l--){var u=e[p=i[l]];if(!U(t,p)||void 0===t[p]&&void 0!==u&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+V(p),value:q(u)}),n.push({op:"remove",path:r+"/"+V(p)}),s=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var c=t[p];"object"==typeof u&&null!=u&&"object"==typeof c&&null!=c&&Array.isArray(u)===Array.isArray(c)?fe(u,c,n,r+"/"+V(p),o):u!==c&&(!0,o&&n.push({op:"test",path:r+"/"+V(p),value:q(u)}),n.push({op:"replace",path:r+"/"+V(p),value:q(c)}))}}if(s||a.length!=i.length)for(l=0;lvoid 0!==t&&e?e[t]:e),e)},applyPatch:function(e,t,n){if(n=n||{},"merge"===(t=s()(s()({},t),{},{path:t.path&&ye(t.path)})).op){const n=Ie(e,t.path);Object.assign(n,t.value),te(e,[ve(t.path,n)])}else if("mergeDeep"===t.op){const n=Ie(e,t.path),r=me()(n,t.value);e=te(e,[ve(t.path,r)]).newDocument}else if("add"===t.op&&""===t.path&&Ae(t.value)){te(e,Object.keys(t.value).reduce(((e,n)=>(e.push({op:"add",path:`/${ye(n)}`,value:t.value[n]}),e)),[]))}else if("replace"===t.op&&""===t.path){let{value:r}=t;n.allowMetaPatches&&t.meta&&je(t)&&(Array.isArray(t.value)||Ae(t.value))&&(r=s()(s()({},r),t.meta)),e=r}else if(te(e,[t]),n.allowMetaPatches&&t.meta&&je(t)&&(Array.isArray(t.value)||Ae(t.value))){const n=Ie(e,t.path),r=s()(s()({},n),t.meta);te(e,[ve(t.path,r)])}return e},parentPathMatch:function(e,t){if(!Array.isArray(t))return!1;for(let n=0,r=t.length;n(e+"").replace(/~/g,"~0").replace(/\//g,"~1"))).join("/")}`:e}function ve(e,t,n){return{op:"replace",path:e,value:t,meta:n}}function be(e,t,n){return Se(_e(e.filter(je).map((e=>t(e.value,n,e.path)))||[]))}function we(e,t,n){return n=n||[],Array.isArray(e)?e.map(((e,r)=>we(e,t,n.concat(r)))):Ae(e)?Object.keys(e).map((r=>we(e[r],t,n.concat(r)))):t(e,n[n.length-1],n)}function Ee(e,t,n){let r=[];if((n=n||[]).length>0){const o=t(e,n[n.length-1],n);o&&(r=r.concat(o))}if(Array.isArray(e)){const o=e.map(((e,r)=>Ee(e,t,n.concat(r))));o&&(r=r.concat(o))}else if(Ae(e)){const o=Object.keys(e).map((r=>Ee(e[r],t,n.concat(r))));o&&(r=r.concat(o))}return r=_e(r),r}function xe(e){return Array.isArray(e)?e:[e]}function _e(e){return[].concat(...e.map((e=>Array.isArray(e)?_e(e):e)))}function Se(e){return e.filter((e=>void 0!==e))}function Ae(e){return e&&"object"==typeof e}function Ce(e){return e&&"function"==typeof e}function ke(e){if(Te(e)){const{op:t}=e;return"add"===t||"remove"===t||"replace"===t}return!1}function Oe(e){return ke(e)||Te(e)&&"mutation"===e.type}function je(e){return Oe(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function Te(e){return e&&"object"==typeof e}function Ie(e,t){try{return X(e,t)}catch(e){return console.error(e),{}}}var Ne=n(8575);function Pe(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=new Array(e),r=0;r-1&&-1===Le.indexOf(n)||Be.indexOf(r)>-1||Fe.some((e=>r.indexOf(e)>-1))}function ze(e,t){const[n,r]=e.split("#"),o=Ne.resolve(n||"",t||"");return r?`${o}#${r}`:o}const qe=/^([a-z]+:\/\/|\/\/)/i,$e=Pe("JSONRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),Ve={},We=new WeakMap,He=[e=>"paths"===e[0]&&"responses"===e[3]&&"examples"===e[5],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"example"===e[7],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[2]&&"example"===e[4],e=>"paths"===e[0]&&"parameters"===e[3]&&"example"===e[5],e=>"paths"===e[0]&&"parameters"===e[2]&&"examples"===e[4]&&"value"===e[6],e=>"paths"===e[0]&&"parameters"===e[3]&&"examples"===e[5]&&"value"===e[7],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[4]&&"example"===e[7],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]],Je={key:"$ref",plugin:(e,t,n,r)=>{const o=r.getInstance(),a=n.slice(0,-1);if(Ue(a)||(e=>He.some((t=>t(e))))(a))return;const{baseDoc:i}=r.getContext(n);if("string"!=typeof e)return new $e("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:i,fullPath:n});const s=Qe(e),l=s[0],u=s[1]||"";let c,p,f;try{c=i||l?Ze(l,i):null}catch(t){return Ye(t,{pointer:u,$ref:e,basePath:c,fullPath:n})}if(function(e,t,n,r){let o=We.get(r);o||(o={},We.set(r,o));const a=function(e){if(0===e.length)return"";return`/${e.map(ot).join("/")}`}(n),i=`${t||""}#${e}`,s=a.replace(/allOf\/\d+\/?/g,""),l=r.contextTree.get([]).baseDoc;if(t===l&&it(s,e))return!0;let u="";const c=n.some((e=>(u=`${u}/${ot(e)}`,o[u]&&o[u].some((e=>it(e,i)||it(i,e))))));if(c)return!0;return void(o[s]=(o[s]||[]).concat(i))}(u,c,a,r)&&!o.useCircularStructures){const t=ze(e,c);return e===t?null:ge.replace(n,t)}if(null==c?(f=nt(u),p=r.get(f),void 0===p&&(p=new $e(`Could not resolve reference: ${e}`,{pointer:u,$ref:e,baseDoc:i,fullPath:n}))):(p=Xe(c,u),p=null!=p.__value?p.__value:p.catch((t=>{throw Ye(t,{pointer:u,$ref:e,baseDoc:i,fullPath:n})}))),p instanceof Error)return[ge.remove(n),p];const h=ze(e,c),d=ge.replace(a,p,{$$ref:h});if(c&&c!==i)return[d,ge.context(a,{baseDoc:c})];try{if(!function(e,t){const n=[e];return t.path.reduce(((e,t)=>(n.push(e[t]),e[t])),e),r(t.value);function r(e){return ge.isObject(e)&&(n.indexOf(e)>=0||Object.keys(e).some((t=>r(e[t]))))}}(r.state,d)||o.useCircularStructures)return d}catch(e){return null}}},Ke=Object.assign(Je,{docCache:Ve,absoluteify:Ze,clearCache:function(e){void 0!==e?delete Ve[e]:Object.keys(Ve).forEach((e=>{delete Ve[e]}))},JSONRefError:$e,wrapError:Ye,getDoc:et,split:Qe,extractFromDoc:Xe,fetchJSON:function(e){return fetch(e,{headers:{Accept:l},loadSpec:!0}).then((e=>e.text())).then((e=>f.ZP.load(e)))},extract:tt,jsonPointerToArray:nt,unescapeJsonPointerToken:rt}),Ge=Ke;function Ze(e,t){if(!qe.test(e)){if(!t)throw new $e(`Tried to resolve a relative URL, without having a basePath. path: '${e}' basePath: '${t}'`);return Ne.resolve(t,e)}return e}function Ye(e,t){let n;return n=e&&e.response&&e.response.body?`${e.response.body.code} ${e.response.body.message}`:e.message,new $e(`Could not resolve reference: ${n}`,t,e)}function Qe(e){return(e+"").split("#")}function Xe(e,t){const n=Ve[e];if(n&&!ge.isPromise(n))try{const e=tt(t,n);return Object.assign(Promise.resolve(e),{__value:e})}catch(e){return Promise.reject(e)}return et(e).then((e=>tt(t,e)))}function et(e){const t=Ve[e];return t?ge.isPromise(t)?t:Promise.resolve(t):(Ve[e]=Ke.fetchJSON(e).then((t=>(Ve[e]=t,t))),Ve[e])}function tt(e,t){const n=nt(e);if(n.length<1)return t;const r=ge.getIn(t,n);if(void 0===r)throw new $e(`Could not resolve pointer: ${e} does not exist in document`,{pointer:e});return r}function nt(e){if("string"!=typeof e)throw new TypeError("Expected a string, got a "+typeof e);return"/"===e[0]&&(e=e.substr(1)),""===e?[]:e.split("/").map(rt)}function rt(e){if("string"!=typeof e)return e;return new URLSearchParams(`=${e.replace(/~1/g,"/").replace(/~0/g,"~")}`).get("")}function ot(e){return new URLSearchParams([["",e.replace(/~/g,"~0").replace(/\//g,"~1")]]).toString().slice(1)}const at=e=>!e||"/"===e||"#"===e;function it(e,t){if(at(t))return!0;const n=e.charAt(t.length),r=t.slice(-1);return 0===e.indexOf(t)&&(!n||"/"===n||"#"===n)&&"#"!==r}const st={key:"allOf",plugin:(e,t,n,r,o)=>{if(o.meta&&o.meta.$$ref)return;const a=n.slice(0,-1);if(Ue(a))return;if(!Array.isArray(e)){const e=new TypeError("allOf must be an array");return e.fullPath=n,e}let i=!1,l=o.value;if(a.forEach((e=>{l&&(l=l[e])})),l=s()({},l),0===Object.keys(l).length)return;delete l.allOf;const u=[];return u.push(r.replace(a,{})),e.forEach(((e,t)=>{if(!r.isObject(e)){if(i)return null;i=!0;const e=new TypeError("Elements in allOf must be objects");return e.fullPath=n,u.push(e)}u.push(r.mergeDeep(a,e));const o=function(e,t){let{specmap:n,getBaseUrlForNodePath:r=(e=>n.getContext([...t,...e]).baseDoc),targetKeys:o=["$ref","$$ref"]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const a=[];return Me()(e).forEach((function(){if(o.includes(this.key)&&"string"==typeof this.node){const e=this.path,o=t.concat(this.path),i=ze(this.node,r(e));a.push(n.replace(o,i))}})),a}(e,n.slice(0,-1),{getBaseUrlForNodePath:e=>r.getContext([...n,t,...e]).baseDoc,specmap:r});u.push(...o)})),l.example&&u.push(r.remove([].concat(a,"example"))),u.push(r.mergeDeep(a,l)),l.$$ref||u.push(r.remove([].concat(a,"$$ref"))),u}},lt={key:"parameters",plugin:(e,t,n,r)=>{if(Array.isArray(e)&&e.length){const t=Object.assign([],e),o=n.slice(0,-1),a=s()({},ge.getIn(r.spec,o));for(let o=0;o{const o=s()({},e);for(const t in e)try{o[t].default=r.modelPropertyMacro(o[t])}catch(e){const t=new Error(e);return t.fullPath=n,t}return ge.replace(n,o)}};class ct{constructor(e){this.root=pt(e||{})}set(e,t){const n=this.getParent(e,!0);if(!n)return void ft(this.root,t,null);const r=e[e.length-1],{children:o}=n;o[r]?ft(o[r],t,n):o[r]=pt(t,n)}get(e){if((e=e||[]).length<1)return this.root.value;let t,n,r=this.root;for(let o=0;o{if(!e)return e;const{children:r}=e;return!r[n]&&t&&(r[n]=pt(null,e)),r[n]}),this.root)}}function pt(e,t){return ft({children:{}},e,t)}function ft(e,t,n){return e.value=t||{},e.protoValue=n?s()(s()({},n.protoValue),e.value):e.value,Object.keys(e.children).forEach((t=>{const n=e.children[t];e.children[t]=ft(n,n.value,e)})),e}const ht=()=>{};class dt{static getPluginName(e){return e.pluginName}static getPatchesOfType(e,t){return e.filter(t)}constructor(e){Object.assign(this,{spec:"",debugLevel:"info",plugins:[],pluginHistory:{},errors:[],mutations:[],promisedPatches:[],state:{},patches:[],context:{},contextTree:new ct,showDebug:!1,allPatches:[],pluginProp:"specMap",libMethods:Object.assign(Object.create(this),ge,{getInstance:()=>this}),allowMetaPatches:!1},e),this.get=this._get.bind(this),this.getContext=this._getContext.bind(this),this.hasRun=this._hasRun.bind(this),this.wrappedPlugins=this.plugins.map(this.wrapPlugin.bind(this)).filter(ge.isFunction),this.patches.push(ge.add([],this.spec)),this.patches.push(ge.context([],this.context)),this.updatePatches(this.patches)}debug(e){if(this.debugLevel===e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r!Array.isArray(e)||e.every(((e,n)=>e===t[n]));return function*(r,o){const a={};for(const e of r.filter(ge.isAdditiveMutation))yield*i(e.value,e.path,e);function*i(r,s,l){if(ge.isObject(r)){const u=s.length-1,c=s[u],p=s.indexOf("properties"),f="properties"===c&&u===p,h=o.allowMetaPatches&&a[r.$$ref];for(const u of Object.keys(r)){const c=r[u],p=s.concat(u),d=ge.isObject(c),m=r.$$ref;if(h||d&&(o.allowMetaPatches&&m&&(a[m]=!0),yield*i(c,p,l)),!f&&u===e.key){const r=t(n,s);n&&!r||(yield e.plugin(c,u,p,o,l))}}}else e.key===s[s.length-1]&&(yield e.plugin(r,e.key,s,o))}}}(e)),Object.assign(r.bind(o),{pluginName:e.name||t,isGenerator:ge.isGenerator(r)})}nextPlugin(){return this.wrappedPlugins.find((e=>this.getMutationsForPlugin(e).length>0))}nextPromisedPatch(){if(this.promisedPatches.length>0)return Promise.race(this.promisedPatches.map((e=>e.value)))}getPluginHistory(e){const t=this.constructor.getPluginName(e);return this.pluginHistory[t]||[]}getPluginRunCount(e){return this.getPluginHistory(e).length}getPluginHistoryTip(e){const t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}getPluginMutationIndex(e){const t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}updatePluginHistory(e,t){const n=this.constructor.getPluginName(e);this.pluginHistory[n]=this.pluginHistory[n]||[],this.pluginHistory[n].push(t)}updatePatches(e){ge.normalizeArray(e).forEach((e=>{if(e instanceof Error)this.errors.push(e);else try{if(!ge.isObject(e))return void this.debug("updatePatches","Got a non-object patch",e);if(this.showDebug&&this.allPatches.push(e),ge.isPromise(e.value))return this.promisedPatches.push(e),void this.promisedPatchThen(e);if(ge.isContextPatch(e))return void this.setContext(e.path,e.value);if(ge.isMutation(e))return void this.updateMutations(e)}catch(e){console.error(e),this.errors.push(e)}}))}updateMutations(e){"object"==typeof e.value&&!Array.isArray(e.value)&&this.allowMetaPatches&&(e.value=s()({},e.value));const t=ge.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}removePromisedPatch(e){const t=this.promisedPatches.indexOf(e);t<0?this.debug("Tried to remove a promisedPatch that isn't there!"):this.promisedPatches.splice(t,1)}promisedPatchThen(e){return e.value=e.value.then((t=>{const n=s()(s()({},e),{},{value:t});this.removePromisedPatch(e),this.updatePatches(n)})).catch((t=>{this.removePromisedPatch(e),this.updatePatches(t)})),e.value}getMutations(e,t){return e=e||0,"number"!=typeof t&&(t=this.mutations.length),this.mutations.slice(e,t)}getCurrentMutations(){return this.getMutationsForPlugin(this.getCurrentPlugin())}getMutationsForPlugin(e){const t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}getCurrentPlugin(){return this.currentPlugin}getLib(){return this.libMethods}_get(e){return ge.getIn(this.state,e)}_getContext(e){return this.contextTree.get(e)}setContext(e,t){return this.contextTree.set(e,t)}_hasRun(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}dispatch(){const e=this,t=this.nextPlugin();if(!t){const e=this.nextPromisedPatch();if(e)return e.then((()=>this.dispatch())).catch((()=>this.dispatch()));const t={spec:this.state,errors:this.errors};return this.showDebug&&(t.patches=this.allPatches),Promise.resolve(t)}if(e.pluginCount=e.pluginCount||{},e.pluginCount[t]=(e.pluginCount[t]||0)+1,e.pluginCount[t]>100)return Promise.resolve({spec:e.state,errors:e.errors.concat(new Error("We've reached a hard limit of 100 plugin runs"))});if(t!==this.currentPlugin&&this.promisedPatches.length){const e=this.promisedPatches.map((e=>e.value));return Promise.all(e.map((e=>e.then(ht,ht)))).then((()=>this.dispatch()))}return function(){e.currentPlugin=t;const r=e.getCurrentMutations(),o=e.mutations.length-1;try{if(t.isGenerator)for(const o of t(r,e.getLib()))n(o);else{n(t(r,e.getLib()))}}catch(e){console.error(e),n([Object.assign(Object.create(e),{plugin:t})])}finally{e.updatePluginHistory(t,{mutationIndex:o})}return e.dispatch()}();function n(n){n&&(n=ge.fullyNormalizeArray(n),e.updatePatches(n,t))}}}const mt={refs:Ge,allOf:st,parameters:lt,properties:ut};var gt=n(32454);function yt(e){const{spec:t}=e,{paths:n}=t,r={};if(!n||t.$$normalized)return e;for(const e in n){const o=n[e];if(null==o||!["object","function"].includes(typeof o))continue;const a=o.parameters;for(const n in o){const i=o[n];if(null==i||!["object","function"].includes(typeof i))continue;const s=(0,gt.Z)(i,e,n);if(s){r[s]?r[s].push(i):r[s]=[i];const e=r[s];if(e.length>1)e.forEach(((e,t)=>{e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=`${s}${t+1}`}));else if(void 0!==i.operationId){const t=e[0];t.__originalOperationId=t.__originalOperationId||i.operationId,t.operationId=s}}if("parameters"!==n){const e=[],n={};for(const r in t)"produces"!==r&&"consumes"!==r&&"security"!==r||(n[r]=t[r],e.push(n));if(a&&(n.parameters=a,e.push(n)),e.length)for(const t of e)for(const e in t)if(i[e]){if("parameters"===e)for(const n of t[e]){i[e].some((e=>e.name&&e.name===n.name||e.$ref&&e.$ref===n.$ref||e.$$ref&&e.$$ref===n.$$ref||e===n))||i[e].push(n)}}else i[e]=t[e]}}}return t.$$normalized=!0,e}async function vt(e){const{spec:t,mode:n,allowMetaPatches:r=!0,pathDiscriminator:o,modelPropertyMacro:a,parameterMacro:i,requestInterceptor:s,responseInterceptor:l,skipNormalization:c,useCircularStructures:p}=e,f=M(e),h=D(e);return function(e){f&&(mt.refs.docCache[f]=e);mt.refs.fetchJSON=u(h,{requestInterceptor:s,responseInterceptor:l});const t=[mt.refs];"function"==typeof i&&t.push(mt.parameters);"function"==typeof a&&t.push(mt.properties);"strict"!==n&&t.push(mt.allOf);return(d={spec:e,context:{baseDoc:f},plugins:t,allowMetaPatches:r,pathDiscriminator:o,parameterMacro:i,modelPropertyMacro:a,useCircularStructures:p},new dt(d).dispatch()).then(c?async e=>e:yt);var d}(t)}const bt={name:"generic",match:()=>!0,normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>vt(e)};const wt=e=>{try{const{openapi:t}=e;return"string"==typeof t&&t.startsWith("3.0")}catch{return!1}},Et=e=>wt(e)||(e=>{try{const{openapi:t}=e;return"string"==typeof t&&t.startsWith("3.1")}catch{return!1}})(e),xt={name:"openapi-2",match(e){let{spec:t}=e;return(e=>{try{const{swagger:t}=e;return"2.0"===t}catch{return!1}})(t)},normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>async function(e){return vt(e)}(e)};const _t={name:"openapi-3-0",match(e){let{spec:t}=e;return wt(t)},normalize(e){let{spec:t}=e;const{spec:n}=yt({spec:t});return n},resolve:async e=>async function(e){return vt(e)}(e)},St=(At={strategies:[_t,xt,bt]},async e=>(async e=>{const{spec:t,requestInterceptor:n,responseInterceptor:r}=e,o=M(e),a=D(e),i=t||await u(a,{requestInterceptor:n,responseInterceptor:r})(o),l=s()(s()({},e),{},{spec:i});return e.strategies.find((e=>e.match(l))).resolve(l)})(s()(s()({},At),e)));var At,Ct=n(88436),kt=n.n(Ct),Ot=n(27361),jt=n.n(Ot),Tt=n(76489);function It(e){return"[object Object]"===Object.prototype.toString.call(e)}function Nt(e){var t,n;return!1!==It(e)&&(void 0===(t=e.constructor)||!1!==It(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf"))}const Pt={body:function(e){let{req:t,value:n}=e;t.body=n},header:function(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){let{req:t,value:n,parameter:r}=e;t.query=t.query||{},!1===n&&"boolean"===r.type&&(n="false");0===n&&["number","integer"].indexOf(r.type)>-1&&(n="0");if(n)t.query[r.name]={collectionFormat:r.collectionFormat,value:n};else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}},path:function(e){let{req:t,value:n,parameter:r}=e;t.url=t.url.split(`{${r.name}}`).join(encodeURIComponent(n))},formData:function(e){let{req:t,value:n,parameter:r}=e;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}};function Rt(e,t){return t.includes("application/json")?"string"==typeof e?e:JSON.stringify(e):e.toString()}function Mt(e){let{req:t,value:n,parameter:r}=e;const{name:o,style:a,explode:i,content:s}=r;if(s){const e=Object.keys(s)[0];return void(t.url=t.url.split(`{${o}}`).join(b(Rt(n,e),{escape:!0})))}const l=w({key:r.name,value:n,style:a||"simple",explode:i||!1,escape:!0});t.url=t.url.split(`{${o}}`).join(l)}function Dt(e){let{req:t,value:n,parameter:r}=e;if(t.query=t.query||{},r.content){const e=Rt(n,Object.keys(r.content)[0]);if(e)t.query[r.name]=e;else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}else if(!1===n&&(n="false"),0===n&&(n="0"),n){const{style:e,explode:o,allowReserved:a}=r;t.query[r.name]={value:n,serializationOption:{style:e,explode:o,allowReserved:a}}}else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}const Lt=["accept","authorization","content-type"];function Bt(e){let{req:t,parameter:n,value:r}=e;if(t.headers=t.headers||{},!(Lt.indexOf(n.name.toLowerCase())>-1))if(n.content){const e=Object.keys(n.content)[0];t.headers[n.name]=Rt(r,e)}else void 0!==r&&(t.headers[n.name]=w({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))}function Ft(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{};const o=typeof r;if(n.content){const e=Object.keys(n.content)[0];t.headers.Cookie=`${n.name}=${Rt(r,e)}`}else if("undefined"!==o){const e="object"===o&&!Array.isArray(r)&&n.explode?"":`${n.name}=`;t.headers.Cookie=e+w({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}const Ut="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{btoa:zt}=Ut,qt=zt;function $t(e,t){const{operation:n,requestBody:r,securities:o,spec:a,attachContentTypeForEmptyPayload:i}=e;let{requestContentType:l}=e;t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const a=s()({},t),{authorized:i={}}=n,l=r.security||o.security||[],u=i&&!!Object.keys(i).length,c=jt()(o,["components","securitySchemes"])||{};if(a.headers=a.headers||{},a.query=a.query||{},!Object.keys(n).length||!u||!l||Array.isArray(r.security)&&!r.security.length)return t;return l.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e],n=c[e];if(!t)return;const r=t.value||t,{type:o}=n;if(t)if("apiKey"===o)"query"===n.in&&(a.query[n.name]=r),"header"===n.in&&(a.headers[n.name]=r),"cookie"===n.in&&(a.cookies[n.name]=r);else if("http"===o){if(/^basic$/i.test(n.scheme)){const e=r.username||"",t=r.password||"",n=qt(`${e}:${t}`);a.headers.Authorization=`Basic ${n}`}/^bearer$/i.test(n.scheme)&&(a.headers.Authorization=`Bearer ${r}`)}else if("oauth2"===o||"openIdConnect"===o){const e=t.token||{},r=e[n["x-tokenName"]||"access_token"];let o=e.token_type;o&&"bearer"!==o.toLowerCase()||(o="Bearer"),a.headers.Authorization=`${o} ${r}`}}))})),a}({request:t,securities:o,operation:n,spec:a});const u=n.requestBody||{},c=Object.keys(u.content||{}),p=l&&c.indexOf(l)>-1;if(r||i){if(l&&p)t.headers["Content-Type"]=l;else if(!l){const e=c[0];e&&(t.headers["Content-Type"]=e,l=e)}}else l&&p&&(t.headers["Content-Type"]=l);if(!e.responseContentType&&n.responses){const e=Object.entries(n.responses).filter((e=>{let[t,n]=e;const r=parseInt(t,10);return r>=200&&r<300&&Nt(n.content)})).reduce(((e,t)=>{let[,n]=t;return e.concat(Object.keys(n.content))}),[]);e.length>0&&(t.headers.accept=e.join(", "))}if(r)if(l){if(c.indexOf(l)>-1)if("application/x-www-form-urlencoded"===l||"multipart/form-data"===l)if("object"==typeof r){const e=(u.content[l]||{}).encoding||{};t.form={},Object.keys(r).forEach((n=>{t.form[n]={value:r[n],encoding:e[n]||{}}}))}else t.form=r;else t.body=r}else t.body=r;return t}function Vt(e,t){const{spec:n,operation:r,securities:o,requestContentType:a,responseContentType:i,attachContentTypeForEmptyPayload:l}=e;if(t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const a=s()({},t),{authorized:i={},specSecurity:l=[]}=n,u=r.security||l,c=i&&!!Object.keys(i).length,p=o.securityDefinitions;if(a.headers=a.headers||{},a.query=a.query||{},!Object.keys(n).length||!c||!u||Array.isArray(r.security)&&!r.security.length)return t;return u.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e];if(!t)return;const{token:n}=t,r=t.value||t,o=p[e],{type:s}=o,l=o["x-tokenName"]||"access_token",u=n&&n[l];let c=n&&n.token_type;if(t)if("apiKey"===s){const e="query"===o.in?"query":"headers";a[e]=a[e]||{},a[e][o.name]=r}else if("basic"===s)if(r.header)a.headers.authorization=r.header;else{const e=r.username||"",t=r.password||"";r.base64=qt(`${e}:${t}`),a.headers.authorization=`Basic ${r.base64}`}else"oauth2"===s&&u&&(c=c&&"bearer"!==c.toLowerCase()?c:"Bearer",a.headers.authorization=`${c} ${u}`)}))})),a}({request:t,securities:o,operation:r,spec:n}),t.body||t.form||l)a?t.headers["Content-Type"]=a:Array.isArray(r.consumes)?[t.headers["Content-Type"]]=r.consumes:Array.isArray(n.consumes)?[t.headers["Content-Type"]]=n.consumes:r.parameters&&r.parameters.filter((e=>"file"===e.type)).length?t.headers["Content-Type"]="multipart/form-data":r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(a){const e=r.parameters&&r.parameters.filter((e=>"body"===e.in)).length>0,n=r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length>0;(e||n)&&(t.headers["Content-Type"]=a)}return!i&&Array.isArray(r.produces)&&r.produces.length>0&&(t.headers.accept=r.produces.join(", ")),t}function Wt(e,t){return`${t.toLowerCase()}-${e}`}const Ht=["http","fetch","spec","operationId","pathName","method","parameters","securities"],Jt=e=>Array.isArray(e)?e:[],Kt=Pe("OperationNotFoundError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),Gt=(e,t)=>t.filter((t=>t.name===e)),Zt=e=>{const t={};e.forEach((e=>{t[e.in]||(t[e.in]={}),t[e.in][e.name]=e}));const n=[];return Object.keys(t).forEach((e=>{Object.keys(t[e]).forEach((r=>{n.push(t[e][r])}))})),n},Yt={buildRequest:Xt};function Qt(e){let{http:t,fetch:n,spec:r,operationId:o,pathName:a,method:i,parameters:l,securities:u}=e,c=kt()(e,Ht);const p=t||n||_;a&&i&&!o&&(o=Wt(a,i));const f=Yt.buildRequest(s()({spec:r,operationId:o,parameters:l,securities:u,http:p},c));return f.body&&(Nt(f.body)||Array.isArray(f.body))&&(f.body=JSON.stringify(f.body)),p(f)}function Xt(e){const{spec:t,operationId:n,responseContentType:r,scheme:o,requestInterceptor:i,responseInterceptor:l,contextUrl:u,userFetch:c,server:p,serverVariables:f,http:h,signal:d}=e;let{parameters:m,parameterBuilders:g}=e;const y=Et(t);g||(g=y?a:Pt);let v={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};d&&(v.signal=d),i&&(v.requestInterceptor=i),l&&(v.responseInterceptor=l),c&&(v.userFetch=c);const b=function(e,t){return e&&e.paths?function(e,t){return function(e,t,n){if(!e||"object"!=typeof e||!e.paths||"object"!=typeof e.paths)return null;const{paths:r}=e;for(const o in r)for(const a in r[o]){if("PARAMETERS"===a.toUpperCase())continue;const i=r[o][a];if(!i||"object"!=typeof i)continue;const s={spec:e,pathName:o,method:a.toUpperCase(),operation:i},l=t(s);if(n&&l)return s}}(e,t,!0)||null}(e,(e=>{let{pathName:n,method:r,operation:o}=e;if(!o||"object"!=typeof o)return!1;const a=o.operationId;return[(0,gt.Z)(o,n,r),Wt(n,r),a].some((e=>e&&e===t))})):null}(t,n);if(!b)throw new Kt(`Operation ${n} not found`);const{operation:w={},method:E,pathName:x}=b;if(v.url+=function(e){const t=Et(e.spec);return t?function(e){let{spec:t,pathName:n,method:r,server:o,contextUrl:a,serverVariables:i={}}=e;const s=jt()(t,["paths",n,(r||"").toLowerCase(),"servers"])||jt()(t,["paths",n,"servers"])||jt()(t,["servers"]);let l="",u=null;if(o&&s&&s.length){const e=s.map((e=>e.url));e.indexOf(o)>-1&&(l=o,u=s[e.indexOf(o)])}!l&&s&&s.length&&(l=s[0].url,[u]=s);if(l.indexOf("{")>-1){(function(e){const t=[],n=/{([^}]+)}/g;let r;for(;r=n.exec(e);)t.push(r[1]);return t})(l).forEach((e=>{if(u.variables&&u.variables[e]){const t=u.variables[e],n=i[e]||t.default,r=new RegExp(`{${e}}`,"g");l=l.replace(r,n)}}))}return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const n=e&&t?Ne.parse(Ne.resolve(t,e)):Ne.parse(e),r=Ne.parse(t),o=en(n.protocol)||en(r.protocol)||"",a=n.host||r.host,i=n.pathname||"";let s;s=o&&a?`${o}://${a+i}`:i;return"/"===s[s.length-1]?s.slice(0,-1):s}(l,a)}(e):function(e){let{spec:t,scheme:n,contextUrl:r=""}=e;const o=Ne.parse(r),a=Array.isArray(t.schemes)?t.schemes[0]:null,i=n||a||en(o.protocol)||"http",s=t.host||o.host||"",l=t.basePath||"";let u;u=i&&s?`${i}://${s+l}`:l;return"/"===u[u.length-1]?u.slice(0,-1):u}(e)}({spec:t,scheme:o,contextUrl:u,server:p,serverVariables:f,pathName:x,method:E}),!n)return delete v.cookies,v;v.url+=x,v.method=`${E}`.toUpperCase(),m=m||{};const _=t.paths[x]||{};r&&(v.headers.accept=r);const S=Zt([].concat(Jt(w.parameters)).concat(Jt(_.parameters)));S.forEach((e=>{const n=g[e.in];let r;if("body"===e.in&&e.schema&&e.schema.properties&&(r=m),r=e&&e.name&&m[e.name],void 0===r?r=e&&e.name&&m[`${e.in}.${e.name}`]:Gt(e.name,S).length>1&&console.warn(`Parameter '${e.name}' is ambiguous because the defined spec has more than one parameter with the name: '${e.name}' and the passed-in parameter values did not define an 'in' value.`),null!==r){if(void 0!==e.default&&void 0===r&&(r=e.default),void 0===r&&e.required&&!e.allowEmptyValue)throw new Error(`Required parameter ${e.name} is not provided`);if(y&&e.schema&&"object"===e.schema.type&&"string"==typeof r)try{r=JSON.parse(r)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}n&&n({req:v,parameter:e,value:r,operation:w,spec:t})}}));const A=s()(s()({},e),{},{operation:w});if(v=y?$t(A,v):Vt(A,v),v.cookies&&Object.keys(v.cookies).length){const e=Object.keys(v.cookies).reduce(((e,t)=>{const n=v.cookies[t];return e+(e?"&":"")+Tt.serialize(t,n)}),"");v.headers.Cookie=e}return v.cookies&&delete v.cookies,R(v),v}const en=e=>e?e.replace(/\W/g,""):null;const tn=async function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{returnEntireTree:r,baseDoc:o,requestInterceptor:a,responseInterceptor:i,parameterMacro:l,modelPropertyMacro:u,useCircularStructures:c,strategies:p}=n,f={spec:e,pathDiscriminator:t,baseDoc:o,requestInterceptor:a,responseInterceptor:i,parameterMacro:l,modelPropertyMacro:u,useCircularStructures:c,strategies:p},h=p.find((e=>e.match(f))).normalize(f),d=await St(s()(s()({},f),{},{spec:h,allowMetaPatches:!0,skipNormalization:!0}));return!r&&Array.isArray(t)&&t.length&&(d.spec=jt()(d.spec,t)||null),d},nn=(e=>async function(t,n){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const o=s()(s()({},e),r);return tn(t,n,o)})({strategies:[_t,xt,bt]});var rn=n(34852);function on(e){let{configs:t,getConfigs:n}=e;return{fn:{fetch:(r=_,o=t.preFetch,a=t.postFetch,a=a||(e=>e),o=o||(e=>e),e=>("string"==typeof e&&(e={url:e}),x.mergeInQueryOrForm(e),e=o(e),a(r(e)))),buildRequest:Xt,execute:Qt,resolve:St,resolveSubtree:function(e,t,r){if(void 0===r){const e=n();r={modelPropertyMacro:e.modelPropertyMacro,parameterMacro:e.parameterMacro,requestInterceptor:e.requestInterceptor,responseInterceptor:e.responseInterceptor}}for(var o=arguments.length,a=new Array(o>3?o-3:0),i=3;i{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(90242);function o(){return{fn:{shallowEqualKeys:r.be}}}},48347:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDisplayName:()=>r});const r=e=>e.displayName||e.name||"Component"},73420:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(35627),o=n.n(r),a=n(90242),i=n(11092),s=n(48347),l=n(60314);const u=e=>{let{getComponents:t,getStore:n,getSystem:r}=e;const u=(c=(0,i.getComponent)(r,n,t),(0,a.HP)(c,(function(){for(var e=arguments.length,t=new Array(e),n=0;n(0,l.Z)(e,(function(){for(var e=arguments.length,t=new Array(e),n=0;n{"use strict";n.r(t),n.d(t,{getComponent:()=>Q,render:()=>Y,withMappedContainer:()=>Z});var r=n(23101),o=n.n(r),a=n(28222),i=n.n(a),s=n(67294),l=n(73935),u=n(97779),c=n(61688),p=n(52798);let f=function(e){e()};const h=()=>f,d=(0,s.createContext)(null);let m=null;var g=n(87462),y=n(63366),v=n(8679),b=n.n(v),w=n(59864);const E=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function x(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:a,areStatePropsEqual:i}){let s,l,u,c,p,f=!1;function h(f,h){const d=!a(h,l),m=!o(f,s,h,l);return s=f,l=h,d&&m?(u=e(s,l),t.dependsOnOwnProps&&(c=t(r,l)),p=n(u,c,l),p):d?(e.dependsOnOwnProps&&(u=e(s,l)),t.dependsOnOwnProps&&(c=t(r,l)),p=n(u,c,l),p):m?function(){const t=e(s,l),r=!i(t,u);return u=t,r&&(p=n(u,c,l)),p}():p}return function(o,a){return f?h(o,a):(s=o,l=a,u=e(s,l),c=t(r,l),p=n(u,c,l),f=!0,p)}}function _(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function S(e){return e.dependsOnOwnProps?Boolean(e.dependsOnOwnProps):1!==e.length}function A(e,t){return function(t,{displayName:n}){const r=function(e,t){return r.dependsOnOwnProps?r.mapToProps(e,t):r.mapToProps(e,void 0)};return r.dependsOnOwnProps=!0,r.mapToProps=function(t,n){r.mapToProps=e,r.dependsOnOwnProps=S(e);let o=r(t,n);return"function"==typeof o&&(r.mapToProps=o,r.dependsOnOwnProps=S(o),o=r(t,n)),o},r}}function C(e,t){return(n,r)=>{throw new Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function k(e,t,n){return(0,g.Z)({},n,e,t)}const O={notify(){},get:()=>[]};function j(e,t){let n,r=O;function o(){i.onStateChange&&i.onStateChange()}function a(){n||(n=t?t.addNestedSub(o):e.subscribe(o),r=function(){const e=h();let t=null,n=null;return{clear(){t=null,n=null},notify(){e((()=>{let e=t;for(;e;)e.callback(),e=e.next}))},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e){let r=!0,o=n={callback:e,next:null,prev:n};return o.prev?o.prev.next=o:t=o,function(){r&&null!==t&&(r=!1,o.next?o.next.prev=o.prev:n=o.prev,o.prev?o.prev.next=o.next:t=o.next)}}}}())}const i={addNestedSub:function(e){return a(),r.subscribe(e)},notifyNestedSubs:function(){r.notify()},handleChangeWrapper:o,isSubscribed:function(){return Boolean(n)},trySubscribe:a,tryUnsubscribe:function(){n&&(n(),n=void 0,r.clear(),r=O)},getListeners:()=>r};return i}const T=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?s.useLayoutEffect:s.useEffect;function I(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function N(e,t){if(I(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r=0;r{throw new Error("uSES not initialized!")};const M=[null,null];function D(e,t,n,r,o,a){e.current=r,n.current=!1,o.current&&(o.current=null,a())}function L(e,t){return e===t}const B=function(e,t,n,{pure:r,areStatesEqual:o=L,areOwnPropsEqual:a=N,areStatePropsEqual:i=N,areMergedPropsEqual:l=N,forwardRef:u=!1,context:c=d}={}){const p=c,f=function(e){return e?"function"==typeof e?A(e):C(e,"mapStateToProps"):_((()=>({})))}(e),h=function(e){return e&&"object"==typeof e?_((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?A(e):C(e,"mapDispatchToProps"):_((e=>({dispatch:e})))}(t),m=function(e){return e?"function"==typeof e?function(e){return function(t,{displayName:n,areMergedPropsEqual:r}){let o,a=!1;return function(t,n,i){const s=e(t,n,i);return a?r(s,o)||(o=s):(a=!0,o=s),o}}}(e):C(e,"mergeProps"):()=>k}(n),v=Boolean(e);return e=>{const t=e.displayName||e.name||"Component",n=`Connect(${t})`,r={shouldHandleStateChanges:v,displayName:n,wrappedComponentName:t,WrappedComponent:e,initMapStateToProps:f,initMapDispatchToProps:h,initMergeProps:m,areStatesEqual:o,areStatePropsEqual:i,areOwnPropsEqual:a,areMergedPropsEqual:l};function c(t){const[n,o,a]=(0,s.useMemo)((()=>{const{reactReduxForwardedRef:e}=t,n=(0,y.Z)(t,P);return[t.context,e,n]}),[t]),i=(0,s.useMemo)((()=>n&&n.Consumer&&(0,w.isContextConsumer)(s.createElement(n.Consumer,null))?n:p),[n,p]),l=(0,s.useContext)(i),u=Boolean(t.store)&&Boolean(t.store.getState)&&Boolean(t.store.dispatch),c=Boolean(l)&&Boolean(l.store);const f=u?t.store:l.store,h=c?l.getServerState:f.getState,d=(0,s.useMemo)((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,a=(0,y.Z)(t,E);return x(n(e,a),r(e,a),o(e,a),e,a)}(f.dispatch,r)),[f]),[m,b]=(0,s.useMemo)((()=>{if(!v)return M;const e=j(f,u?void 0:l.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[f,u,l]),_=(0,s.useMemo)((()=>u?l:(0,g.Z)({},l,{subscription:m})),[u,l,m]),S=(0,s.useRef)(),A=(0,s.useRef)(a),C=(0,s.useRef)(),k=(0,s.useRef)(!1),O=((0,s.useRef)(!1),(0,s.useRef)(!1)),I=(0,s.useRef)();T((()=>(O.current=!0,()=>{O.current=!1})),[]);const N=(0,s.useMemo)((()=>()=>C.current&&a===A.current?C.current:d(f.getState(),a)),[f,a]),L=(0,s.useMemo)((()=>e=>m?function(e,t,n,r,o,a,i,s,l,u,c){if(!e)return()=>{};let p=!1,f=null;const h=()=>{if(p||!s.current)return;const e=t.getState();let n,h;try{n=r(e,o.current)}catch(e){h=e,f=e}h||(f=null),n===a.current?i.current||u():(a.current=n,l.current=n,i.current=!0,c())};return n.onStateChange=h,n.trySubscribe(),h(),()=>{if(p=!0,n.tryUnsubscribe(),n.onStateChange=null,f)throw f}}(v,f,m,d,A,S,k,O,C,b,e):()=>{}),[m]);var B,F,U;let z;B=D,F=[A,S,k,a,C,b],T((()=>B(...F)),U);try{z=R(L,N,h?()=>d(h(),a):N)}catch(e){throw I.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${I.current.stack}\n\n`),e}T((()=>{I.current=void 0,C.current=void 0,S.current=z}));const q=(0,s.useMemo)((()=>s.createElement(e,(0,g.Z)({},z,{ref:o}))),[o,e,z]);return(0,s.useMemo)((()=>v?s.createElement(i.Provider,{value:_},q):q),[i,q,_])}const d=s.memo(c);if(d.WrappedComponent=e,d.displayName=c.displayName=n,u){const t=s.forwardRef((function(e,t){return s.createElement(d,(0,g.Z)({},e,{reactReduxForwardedRef:t}))}));return t.displayName=n,t.WrappedComponent=e,b()(t,e)}return b()(d,e)}};const F=function({store:e,context:t,children:n,serverState:r}){const o=(0,s.useMemo)((()=>{const t=j(e);return{store:e,subscription:t,getServerState:r?()=>r:void 0}}),[e,r]),a=(0,s.useMemo)((()=>e.getState()),[e]);T((()=>{const{subscription:t}=o;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),a!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[o,a]);const i=t||d;return s.createElement(i.Provider,{value:o},n)};var U,z;U=p.useSyncExternalStoreWithSelector,m=U,(e=>{R=e})(c.useSyncExternalStore),z=l.unstable_batchedUpdates,f=z;var q=n(57557),$=n.n(q),V=n(6557),W=n.n(V);const H=e=>t=>{const{fn:n}=e();class r extends s.Component{render(){return s.createElement(t,o()({},e(),this.props,this.context))}}return r.displayName=`WithSystem(${n.getDisplayName(t)})`,r},J=(e,t)=>n=>{const{fn:r}=e();class a extends s.Component{render(){return s.createElement(F,{store:t},s.createElement(n,o()({},this.props,this.context)))}}return a.displayName=`WithRoot(${r.getDisplayName(n)})`,a},K=(e,t,n)=>(0,u.qC)(n?J(e,n):W(),B(((n,r)=>{var o;const a={...r,...e()},i=(null===(o=t.prototype)||void 0===o?void 0:o.mapStateToProps)||(e=>({state:e}));return i(n,a)})),H(e))(t),G=(e,t,n,r)=>{for(const o in t){const a=t[o];"function"==typeof a&&a(n[o],r[o],e())}},Z=(e,t,n)=>(t,r)=>{const{fn:o}=e(),a=n(t,"root");class l extends s.Component{constructor(t,n){super(t,n),G(e,r,t,{})}UNSAFE_componentWillReceiveProps(t){G(e,r,t,this.props)}render(){const e=$()(this.props,r?i()(r):[]);return s.createElement(a,e)}}return l.displayName=`WithMappedContainer(${o.getDisplayName(a)})`,l},Y=(e,t,n,r)=>o=>{const a=n(e,t,r)("App","root");l.render(s.createElement(a,null),o)},Q=(e,t,n)=>function(r,o){let a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("string"!=typeof r)throw new TypeError("Need a string, to fetch a component. Was given a "+typeof r);const i=n(r);return i?o?"root"===o?K(e,i,t()):K(e,i):i:(a.failSilently||e().log.warn("Could not find component:",r),null)}},33424:(e,t,n)=>{"use strict";n.d(t,{d3:()=>D,C2:()=>ee});var r=n(28222),o=n.n(r),a=n(58118),i=n.n(a),s=n(63366);function l(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return function(e){if(0===e.length||1===e.length)return e;var t,n,r=e.join(".");return m[r]||(m[r]=0===(n=(t=e).length)||1===n?t:2===n?[t[0],t[1],"".concat(t[0],".").concat(t[1]),"".concat(t[1],".").concat(t[0])]:3===n?[t[0],t[1],t[2],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0])]:n>=4?[t[0],t[1],t[2],t[3],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[2],".").concat(t[3]),"".concat(t[3],".").concat(t[0]),"".concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[0]),"".concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[3],".").concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[2],".").concat(t[1],".").concat(t[0])]:void 0),m[r]}(e.filter((function(e){return"token"!==e}))).reduce((function(e,t){return d(d({},e),n[t])}),t)}function y(e){return e.join(" ")}function v(e){var t=e.node,n=e.stylesheet,r=e.style,o=void 0===r?{}:r,a=e.useInlineStyles,i=e.key,s=t.properties,l=t.type,u=t.tagName,c=t.value;if("text"===l)return c;if(u){var h,m=function(e,t){var n=0;return function(r){return n+=1,r.map((function(r,o){return v({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(o)})}))}}(n,a);if(a){var b=Object.keys(n).reduce((function(e,t){return t.split(".").forEach((function(t){e.includes(t)||e.push(t)})),e}),[]),w=s.className&&s.className.includes("token")?["token"]:[],E=s.className&&w.concat(s.className.filter((function(e){return!b.includes(e)})));h=d(d({},s),{},{className:y(E)||void 0,style:g(s.className,Object.assign({},s.style,o),n)})}else h=d(d({},s),{},{className:y(s.className)});var x=m(t.children);return p.createElement(u,(0,f.Z)({key:i},h),x)}}const b=function(e,t){return-1!==e.listLanguages().indexOf(t)};var w=["language","children","style","customStyle","codeTagProps","useInlineStyles","showLineNumbers","showInlineLineNumbers","startingLineNumber","lineNumberContainerStyle","lineNumberStyle","wrapLines","wrapLongLines","lineProps","renderer","PreTag","CodeTag","code","astGenerator"];function E(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function x(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return t||u.length>0?function(e,t){return k({children:e,lineNumber:t,lineNumberStyle:s,largestLineNumber:i,showInlineLineNumbers:o,lineProps:n,className:arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],showLineNumbers:r,wrapLongLines:l})}(e,a,u):function(e,t){if(r&&t&&o){var n=C(s,t,i);e.unshift(A(t,n))}return e}(e,a)}for(var m=function(){var e=c[h],t=e.children[0].value;if(t.match(_)){var n=t.split("\n");n.forEach((function(t,o){var i=r&&p.length+a,s={type:"text",value:"".concat(t,"\n")};if(0===o){var l=d(c.slice(f+1,h).concat(k({children:[s],className:e.properties.className})),i);p.push(l)}else if(o===n.length-1){var u=c[h+1]&&c[h+1].children&&c[h+1].children[0],m={type:"text",value:"".concat(t)};if(u){var g=k({children:[m],className:e.properties.className});c.splice(h+1,0,g)}else{var y=d([m],i,e.properties.className);p.push(y)}}else{var v=d([s],i,e.properties.className);p.push(v)}})),f=h}h++};h=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,w);$=$||N;var W=d?p.createElement(S,{containerStyle:E,codeStyle:u.style||{},numberStyle:A,startingLineNumber:v,codeString:q}):null,H=o.hljs||o['pre[class*="language-"]']||{backgroundColor:"#fff"},J=I($)?"hljs":"prismjs",K=f?Object.assign({},V,{style:Object.assign({},H,i)}):Object.assign({},V,{className:V.className?"".concat(J," ").concat(V.className):J,style:Object.assign({},i)});if(u.style=x(x({},u.style),{},O?{whiteSpace:"pre-wrap"}:{whiteSpace:"pre"}),!$)return p.createElement(B,K,W,p.createElement(U,u,q));(void 0===C&&D||O)&&(C=!0),D=D||T;var G=[{type:"text",value:q}],Z=function(e){var t=e.astGenerator,n=e.language,r=e.code,o=e.defaultCodeValue;if(I(t)){var a=b(t,n);return"text"===n?{value:o,language:"text"}:a?t.highlight(n,r):t.highlightAuto(r)}try{return n&&"text"!==n?{value:t.highlight(r,n)}:{value:o}}catch(e){return{value:o}}}({astGenerator:$,language:t,code:q,defaultCodeValue:G});null===Z.language&&(Z.value=G);var Y=j(Z,C,M,d,g,v,Z.value.length+v,A,O);return p.createElement(B,K,p.createElement(U,u,!g&&W,D({rows:Y,stylesheet:o,useInlineStyles:f})))});M.registerLanguage=R.registerLanguage;const D=M;var L=n(96344);const B=n.n(L)();var F=n(82026);const U=n.n(F)();var z=n(42157);const q=n.n(z)();var $=n(61519);const V=n.n($)();var W=n(54587);const H=n.n(W)();var J=n(30786);const K=n.n(J)();var G=n(66336);const Z=n.n(G)(),Y={hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#333",color:"white"},"hljs-name":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-code":{fontStyle:"italic",color:"#888"},"hljs-emphasis":{fontStyle:"italic"},"hljs-tag":{color:"#62c8f3"},"hljs-variable":{color:"#ade5fc"},"hljs-template-variable":{color:"#ade5fc"},"hljs-selector-id":{color:"#ade5fc"},"hljs-selector-class":{color:"#ade5fc"},"hljs-string":{color:"#a2fca2"},"hljs-bullet":{color:"#d36363"},"hljs-type":{color:"#ffa"},"hljs-title":{color:"#ffa"},"hljs-section":{color:"#ffa"},"hljs-attribute":{color:"#ffa"},"hljs-quote":{color:"#ffa"},"hljs-built_in":{color:"#ffa"},"hljs-builtin-name":{color:"#ffa"},"hljs-number":{color:"#d36363"},"hljs-symbol":{color:"#d36363"},"hljs-keyword":{color:"#fcc28c"},"hljs-selector-tag":{color:"#fcc28c"},"hljs-literal":{color:"#fcc28c"},"hljs-comment":{color:"#888"},"hljs-deletion":{color:"#333",backgroundColor:"#fc9b9b"},"hljs-regexp":{color:"#c6b4f0"},"hljs-link":{color:"#c6b4f0"},"hljs-meta":{color:"#fc9b9b"},"hljs-addition":{backgroundColor:"#a2fca2",color:"#333"}};D.registerLanguage("json",U),D.registerLanguage("js",B),D.registerLanguage("xml",q),D.registerLanguage("yaml",H),D.registerLanguage("http",K),D.registerLanguage("bash",V),D.registerLanguage("powershell",Z),D.registerLanguage("javascript",B);const Q={agate:Y,arta:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#222",color:"#aaa"},"hljs-subst":{color:"#aaa"},"hljs-section":{color:"#fff",fontWeight:"bold"},"hljs-comment":{color:"#444"},"hljs-quote":{color:"#444"},"hljs-meta":{color:"#444"},"hljs-string":{color:"#ffcc33"},"hljs-symbol":{color:"#ffcc33"},"hljs-bullet":{color:"#ffcc33"},"hljs-regexp":{color:"#ffcc33"},"hljs-number":{color:"#00cc66"},"hljs-addition":{color:"#00cc66"},"hljs-built_in":{color:"#32aaee"},"hljs-builtin-name":{color:"#32aaee"},"hljs-literal":{color:"#32aaee"},"hljs-type":{color:"#32aaee"},"hljs-template-variable":{color:"#32aaee"},"hljs-attribute":{color:"#32aaee"},"hljs-link":{color:"#32aaee"},"hljs-keyword":{color:"#6644aa"},"hljs-selector-tag":{color:"#6644aa"},"hljs-name":{color:"#6644aa"},"hljs-selector-id":{color:"#6644aa"},"hljs-selector-class":{color:"#6644aa"},"hljs-title":{color:"#bb1166"},"hljs-variable":{color:"#bb1166"},"hljs-deletion":{color:"#bb1166"},"hljs-template-tag":{color:"#bb1166"},"hljs-doctag":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-emphasis":{fontStyle:"italic"}},monokai:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#272822",color:"#ddd"},"hljs-tag":{color:"#f92672"},"hljs-keyword":{color:"#f92672",fontWeight:"bold"},"hljs-selector-tag":{color:"#f92672",fontWeight:"bold"},"hljs-literal":{color:"#f92672",fontWeight:"bold"},"hljs-strong":{color:"#f92672"},"hljs-name":{color:"#f92672"},"hljs-code":{color:"#66d9ef"},"hljs-class .hljs-title":{color:"white"},"hljs-attribute":{color:"#bf79db"},"hljs-symbol":{color:"#bf79db"},"hljs-regexp":{color:"#bf79db"},"hljs-link":{color:"#bf79db"},"hljs-string":{color:"#a6e22e"},"hljs-bullet":{color:"#a6e22e"},"hljs-subst":{color:"#a6e22e"},"hljs-title":{color:"#a6e22e",fontWeight:"bold"},"hljs-section":{color:"#a6e22e",fontWeight:"bold"},"hljs-emphasis":{color:"#a6e22e"},"hljs-type":{color:"#a6e22e",fontWeight:"bold"},"hljs-built_in":{color:"#a6e22e"},"hljs-builtin-name":{color:"#a6e22e"},"hljs-selector-attr":{color:"#a6e22e"},"hljs-selector-pseudo":{color:"#a6e22e"},"hljs-addition":{color:"#a6e22e"},"hljs-variable":{color:"#a6e22e"},"hljs-template-tag":{color:"#a6e22e"},"hljs-template-variable":{color:"#a6e22e"},"hljs-comment":{color:"#75715e"},"hljs-quote":{color:"#75715e"},"hljs-deletion":{color:"#75715e"},"hljs-meta":{color:"#75715e"},"hljs-doctag":{fontWeight:"bold"},"hljs-selector-id":{fontWeight:"bold"}},nord:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#2E3440",color:"#D8DEE9"},"hljs-subst":{color:"#D8DEE9"},"hljs-selector-tag":{color:"#81A1C1"},"hljs-selector-id":{color:"#8FBCBB",fontWeight:"bold"},"hljs-selector-class":{color:"#8FBCBB"},"hljs-selector-attr":{color:"#8FBCBB"},"hljs-selector-pseudo":{color:"#88C0D0"},"hljs-addition":{backgroundColor:"rgba(163, 190, 140, 0.5)"},"hljs-deletion":{backgroundColor:"rgba(191, 97, 106, 0.5)"},"hljs-built_in":{color:"#8FBCBB"},"hljs-type":{color:"#8FBCBB"},"hljs-class":{color:"#8FBCBB"},"hljs-function":{color:"#88C0D0"},"hljs-function > .hljs-title":{color:"#88C0D0"},"hljs-keyword":{color:"#81A1C1"},"hljs-literal":{color:"#81A1C1"},"hljs-symbol":{color:"#81A1C1"},"hljs-number":{color:"#B48EAD"},"hljs-regexp":{color:"#EBCB8B"},"hljs-string":{color:"#A3BE8C"},"hljs-title":{color:"#8FBCBB"},"hljs-params":{color:"#D8DEE9"},"hljs-bullet":{color:"#81A1C1"},"hljs-code":{color:"#8FBCBB"},"hljs-emphasis":{fontStyle:"italic"},"hljs-formula":{color:"#8FBCBB"},"hljs-strong":{fontWeight:"bold"},"hljs-link:hover":{textDecoration:"underline"},"hljs-quote":{color:"#4C566A"},"hljs-comment":{color:"#4C566A"},"hljs-doctag":{color:"#8FBCBB"},"hljs-meta":{color:"#5E81AC"},"hljs-meta-keyword":{color:"#5E81AC"},"hljs-meta-string":{color:"#A3BE8C"},"hljs-attr":{color:"#8FBCBB"},"hljs-attribute":{color:"#D8DEE9"},"hljs-builtin-name":{color:"#81A1C1"},"hljs-name":{color:"#81A1C1"},"hljs-section":{color:"#88C0D0"},"hljs-tag":{color:"#81A1C1"},"hljs-variable":{color:"#D8DEE9"},"hljs-template-variable":{color:"#D8DEE9"},"hljs-template-tag":{color:"#5E81AC"},"abnf .hljs-attribute":{color:"#88C0D0"},"abnf .hljs-symbol":{color:"#EBCB8B"},"apache .hljs-attribute":{color:"#88C0D0"},"apache .hljs-section":{color:"#81A1C1"},"arduino .hljs-built_in":{color:"#88C0D0"},"aspectj .hljs-meta":{color:"#D08770"},"aspectj > .hljs-title":{color:"#88C0D0"},"bnf .hljs-attribute":{color:"#8FBCBB"},"clojure .hljs-name":{color:"#88C0D0"},"clojure .hljs-symbol":{color:"#EBCB8B"},"coq .hljs-built_in":{color:"#88C0D0"},"cpp .hljs-meta-string":{color:"#8FBCBB"},"css .hljs-built_in":{color:"#88C0D0"},"css .hljs-keyword":{color:"#D08770"},"diff .hljs-meta":{color:"#8FBCBB"},"ebnf .hljs-attribute":{color:"#8FBCBB"},"glsl .hljs-built_in":{color:"#88C0D0"},"groovy .hljs-meta:not(:first-child)":{color:"#D08770"},"haxe .hljs-meta":{color:"#D08770"},"java .hljs-meta":{color:"#D08770"},"ldif .hljs-attribute":{color:"#8FBCBB"},"lisp .hljs-name":{color:"#88C0D0"},"lua .hljs-built_in":{color:"#88C0D0"},"moonscript .hljs-built_in":{color:"#88C0D0"},"nginx .hljs-attribute":{color:"#88C0D0"},"nginx .hljs-section":{color:"#5E81AC"},"pf .hljs-built_in":{color:"#88C0D0"},"processing .hljs-built_in":{color:"#88C0D0"},"scss .hljs-keyword":{color:"#81A1C1"},"stylus .hljs-keyword":{color:"#81A1C1"},"swift .hljs-meta":{color:"#D08770"},"vim .hljs-built_in":{color:"#88C0D0",fontStyle:"italic"},"yaml .hljs-meta":{color:"#D08770"}},obsidian:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#282b2e",color:"#e0e2e4"},"hljs-keyword":{color:"#93c763",fontWeight:"bold"},"hljs-selector-tag":{color:"#93c763",fontWeight:"bold"},"hljs-literal":{color:"#93c763",fontWeight:"bold"},"hljs-selector-id":{color:"#93c763"},"hljs-number":{color:"#ffcd22"},"hljs-attribute":{color:"#668bb0"},"hljs-code":{color:"white"},"hljs-class .hljs-title":{color:"white"},"hljs-section":{color:"white",fontWeight:"bold"},"hljs-regexp":{color:"#d39745"},"hljs-link":{color:"#d39745"},"hljs-meta":{color:"#557182"},"hljs-tag":{color:"#8cbbad"},"hljs-name":{color:"#8cbbad",fontWeight:"bold"},"hljs-bullet":{color:"#8cbbad"},"hljs-subst":{color:"#8cbbad"},"hljs-emphasis":{color:"#8cbbad"},"hljs-type":{color:"#8cbbad",fontWeight:"bold"},"hljs-built_in":{color:"#8cbbad"},"hljs-selector-attr":{color:"#8cbbad"},"hljs-selector-pseudo":{color:"#8cbbad"},"hljs-addition":{color:"#8cbbad"},"hljs-variable":{color:"#8cbbad"},"hljs-template-tag":{color:"#8cbbad"},"hljs-template-variable":{color:"#8cbbad"},"hljs-string":{color:"#ec7600"},"hljs-symbol":{color:"#ec7600"},"hljs-comment":{color:"#818e96"},"hljs-quote":{color:"#818e96"},"hljs-deletion":{color:"#818e96"},"hljs-selector-class":{color:"#A082BD"},"hljs-doctag":{fontWeight:"bold"},"hljs-title":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"}},"tomorrow-night":{"hljs-comment":{color:"#969896"},"hljs-quote":{color:"#969896"},"hljs-variable":{color:"#cc6666"},"hljs-template-variable":{color:"#cc6666"},"hljs-tag":{color:"#cc6666"},"hljs-name":{color:"#cc6666"},"hljs-selector-id":{color:"#cc6666"},"hljs-selector-class":{color:"#cc6666"},"hljs-regexp":{color:"#cc6666"},"hljs-deletion":{color:"#cc6666"},"hljs-number":{color:"#de935f"},"hljs-built_in":{color:"#de935f"},"hljs-builtin-name":{color:"#de935f"},"hljs-literal":{color:"#de935f"},"hljs-type":{color:"#de935f"},"hljs-params":{color:"#de935f"},"hljs-meta":{color:"#de935f"},"hljs-link":{color:"#de935f"},"hljs-attribute":{color:"#f0c674"},"hljs-string":{color:"#b5bd68"},"hljs-symbol":{color:"#b5bd68"},"hljs-bullet":{color:"#b5bd68"},"hljs-addition":{color:"#b5bd68"},"hljs-title":{color:"#81a2be"},"hljs-section":{color:"#81a2be"},"hljs-keyword":{color:"#b294bb"},"hljs-selector-tag":{color:"#b294bb"},hljs:{display:"block",overflowX:"auto",background:"#1d1f21",color:"#c5c8c6",padding:"0.5em"},"hljs-emphasis":{fontStyle:"italic"},"hljs-strong":{fontWeight:"bold"}}},X=o()(Q),ee=e=>i()(X).call(X,e)?Q[e]:(console.warn(`Request style '${e}' is not available, returning default instead`),Y)},90242:(e,t,n)=>{"use strict";n.d(t,{AF:()=>he,Ay:()=>be,D$:()=>ut,DR:()=>Se,GZ:()=>Qe,HP:()=>ve,Ik:()=>qe,J6:()=>ot,Kn:()=>me,LQ:()=>de,Nm:()=>et,O2:()=>mt,Pz:()=>lt,Q2:()=>we,QG:()=>nt,UG:()=>Ge,Uj:()=>ft,V9:()=>ct,Wl:()=>ge,XV:()=>st,Xb:()=>ht,Zl:()=>Ae,_5:()=>Ee,be:()=>Xe,cz:()=>pt,gp:()=>_e,hW:()=>tt,iQ:()=>xe,kJ:()=>ye,mz:()=>pe,nX:()=>at,oG:()=>fe,oJ:()=>rt,po:()=>it,r3:()=>Ze,wh:()=>Ye,xi:()=>Ke});var r=n(58309),o=n.n(r),a=n(97606),i=n.n(a),s=n(74386),l=n.n(s),u=n(86),c=n.n(u),p=n(14418),f=n.n(p),h=n(28222),d=n.n(h),m=(n(11189),n(24282)),g=n.n(m),y=n(76986),v=n.n(y),b=n(2578),w=n.n(b),E=n(24278),x=n.n(E),_=(n(39022),n(92039)),S=n.n(_),A=(n(58118),n(35627)),C=n.n(A),k=n(11882),O=n.n(k),j=n(51679),T=n.n(j),I=n(27043),N=n.n(I),P=n(81607),R=n.n(P),M=n(43393),D=n.n(M),L=n(17967),B=n(68929),F=n.n(B),U=n(11700),z=n.n(U),q=n(88306),$=n.n(q),V=n(13311),W=n.n(V),H=n(59704),J=n.n(H),K=n(77813),G=n.n(K),Z=n(23560),Y=n.n(Z),Q=n(57050),X=n(27504),ee=n(8269),te=n.n(ee),ne=n(19069),re=n(92282),oe=n.n(re),ae=n(89072),ie=n.n(ae),se=n(1272),le=n(48764).Buffer;const ue="default",ce=e=>D().Iterable.isIterable(e);function pe(e){return me(e)?ce(e)?e.toJS():e:{}}function fe(e){var t,n;if(ce(e))return e;if(e instanceof X.Z.File)return e;if(!me(e))return e;if(o()(e))return i()(n=D().Seq(e)).call(n,fe).toList();if(Y()(l()(e))){var r;const t=function(e){if(!Y()(l()(e)))return e;const t={},n="_**[]",r={};for(let o of l()(e).call(e))if(t[o[0]]||r[o[0]]&&r[o[0]].containsMultiple){if(!r[o[0]]){r[o[0]]={containsMultiple:!0,length:1},t[`${o[0]}${n}${r[o[0]].length}`]=t[o[0]],delete t[o[0]]}r[o[0]].length+=1,t[`${o[0]}${n}${r[o[0]].length}`]=o[1]}else t[o[0]]=o[1];return t}(e);return i()(r=D().OrderedMap(t)).call(r,fe)}return i()(t=D().OrderedMap(e)).call(t,fe)}function he(e){return o()(e)?e:[e]}function de(e){return"function"==typeof e}function me(e){return!!e&&"object"==typeof e}function ge(e){return"function"==typeof e}function ye(e){return o()(e)}const ve=$();function be(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>(n[r]=t(e[r],r),n)),{})}function we(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>{let o=t(e[r],r);return o&&"object"==typeof o&&v()(n,o),n}),{})}function Ee(e){return t=>{let{dispatch:n,getState:r}=t;return t=>n=>"function"==typeof n?n(e()):t(n)}}function xe(e){var t;let n=e.keySeq();return n.contains(ue)?ue:w()(t=f()(n).call(n,(e=>"2"===(e+"")[0]))).call(t).first()}function _e(e,t){if(!D().Iterable.isIterable(e))return D().List();let n=e.getIn(o()(t)?t:[t]);return D().List.isList(n)?n:D().List()}function Se(e){let t,n=[/filename\*=[^']+'\w*'"([^"]+)";?/i,/filename\*=[^']+'\w*'([^;]+);?/i,/filename="([^;]*);?"/i,/filename=([^;]*);?/i];if(S()(n).call(n,(n=>(t=n.exec(e),null!==t))),null!==t&&t.length>1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null}function Ae(e){return t=e.replace(/\.[^./]*$/,""),z()(F()(t));var t}const Ce=(e,t)=>{if(e>t)return`Value must be less than ${t}`},ke=(e,t)=>{if(e{if(!/^-?\d+(\.?\d+)?$/.test(e))return"Value must be a number"},je=e=>{if(!/^-?\d+$/.test(e))return"Value must be an integer"},Te=e=>{if(e&&!(e instanceof X.Z.File))return"Value must be a file"},Ie=e=>{if("true"!==e&&"false"!==e&&!0!==e&&!1!==e)return"Value must be a boolean"},Ne=e=>{if(e&&"string"!=typeof e)return"Value must be a string"},Pe=e=>{if(isNaN(Date.parse(e)))return"Value must be a DateTime"},Re=e=>{if(e=e.toString().toLowerCase(),!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(e))return"Value must be a Guid"},Me=(e,t)=>{if(e.length>t)return`Value must be no longer than ${t} character${1!==t?"s":""}`},De=(e,t)=>{if(e&&("true"===t||!0===t)){const t=(0,M.fromJS)(e),n=t.toSet();if(e.length>n.size){let e=(0,M.Set)();if(c()(t).call(t,((n,r)=>{f()(t).call(t,(e=>ge(e.equals)?e.equals(n):e===n)).size>1&&(e=e.add(r))})),0!==e.size)return i()(e).call(e,(e=>({index:e,error:"No duplicates allowed."}))).toArray()}}},Le=(e,t)=>{if(!e&&t>=1||e&&e.length{if(e&&e.length>t)return`Array must not contain more then ${t} item${1===t?"":"s"}`},Fe=(e,t)=>{if(e.length{if(!new RegExp(t).test(e))return"Value must follow pattern "+t};function ze(e,t,n,r,a){if(!t)return[];let s=[],l=t.get("nullable"),u=t.get("required"),p=t.get("maximum"),f=t.get("minimum"),h=t.get("type"),d=t.get("format"),m=t.get("maxLength"),g=t.get("minLength"),y=t.get("uniqueItems"),v=t.get("maxItems"),b=t.get("minItems"),w=t.get("pattern");const E=n||!0===u,x=null!=e;if(l&&null===e||!h||!(E||x&&"array"===h||!(!E&&!x)))return[];let _="string"===h&&e,A="array"===h&&o()(e)&&e.length,C="array"===h&&D().List.isList(e)&&e.count();const k=[_,A,C,"array"===h&&"string"==typeof e&&e,"file"===h&&e instanceof X.Z.File,"boolean"===h&&(e||!1===e),"number"===h&&(e||0===e),"integer"===h&&(e||0===e),"object"===h&&"object"==typeof e&&null!==e,"object"===h&&"string"==typeof e&&e],O=S()(k).call(k,(e=>!!e));if(E&&!O&&!r)return s.push("Required field is not provided"),s;if("object"===h&&(null===a||"application/json"===a)){let n=e;if("string"==typeof e)try{n=JSON.parse(e)}catch(e){return s.push("Parameter string value must be valid JSON"),s}var j;if(t&&t.has("required")&&ge(u.isList)&&u.isList()&&c()(u).call(u,(e=>{void 0===n[e]&&s.push({propKey:e,error:"Required property not found"})})),t&&t.has("properties"))c()(j=t.get("properties")).call(j,((e,t)=>{const o=ze(n[t],e,!1,r,a);s.push(...i()(o).call(o,(e=>({propKey:t,error:e}))))}))}if(w){let t=Ue(e,w);t&&s.push(t)}if(b&&"array"===h){let t=Le(e,b);t&&s.push(t)}if(v&&"array"===h){let t=Be(e,v);t&&s.push({needRemove:!0,error:t})}if(y&&"array"===h){let t=De(e,y);t&&s.push(...t)}if(m||0===m){let t=Me(e,m);t&&s.push(t)}if(g){let t=Fe(e,g);t&&s.push(t)}if(p||0===p){let t=Ce(e,p);t&&s.push(t)}if(f||0===f){let t=ke(e,f);t&&s.push(t)}if("string"===h){let t;if(t="date-time"===d?Pe(e):"uuid"===d?Re(e):Ne(e),!t)return s;s.push(t)}else if("boolean"===h){let t=Ie(e);if(!t)return s;s.push(t)}else if("number"===h){let t=Oe(e);if(!t)return s;s.push(t)}else if("integer"===h){let t=je(e);if(!t)return s;s.push(t)}else if("array"===h){if(!A&&!C)return s;e&&c()(e).call(e,((e,n)=>{const o=ze(e,t.get("items"),!1,r,a);s.push(...i()(o).call(o,(e=>({index:n,error:e}))))}))}else if("file"===h){let t=Te(e);if(!t)return s;s.push(t)}return s}const qe=function(e,t){let{isOAS3:n=!1,bypassRequiredCheck:r=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=e.get("required"),{schema:a,parameterContentMediaType:i}=(0,ne.Z)(e,{isOAS3:n});return ze(t,a,o,r,i)},$e=(e,t,n)=>{if(e&&!e.xml&&(e.xml={}),e&&!e.xml.name){if(!e.$$ref&&(e.type||e.items||e.properties||e.additionalProperties))return'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e';if(e.$$ref){let t=e.$$ref.match(/\S*\/(\S+)$/);e.xml.name=t[1]}}return(0,Q.memoizedCreateXMLExample)(e,t,n)},Ve=[{when:/json/,shouldStringifyTypes:["string"]}],We=["object"],He=(e,t,n,r)=>{const o=(0,Q.memoizedSampleFromSchema)(e,t,r),a=typeof o,i=g()(Ve).call(Ve,((e,t)=>t.when.test(n)?[...e,...t.shouldStringifyTypes]:e),We);return J()(i,(e=>e===a))?C()(o,null,2):o},Je=(e,t,n,r)=>{const o=He(e,t,n,r);let a;try{a=se.ZP.dump(se.ZP.load(o),{lineWidth:-1},{schema:se.A8}),"\n"===a[a.length-1]&&(a=x()(a).call(a,0,a.length-1))}catch(e){return console.error(e),"error: could not generate yaml example"}return a.replace(/\t/g," ")},Ke=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:void 0;return e&&ge(e.toJS)&&(e=e.toJS()),r&&ge(r.toJS)&&(r=r.toJS()),/xml/.test(t)?$e(e,n,r):/(yaml|yml)/.test(t)?Je(e,n,t,r):He(e,n,t,r)},Ge=()=>{let e={},t=X.Z.location.search;if(!t)return{};if(""!=t){let n=t.substr(1).split("&");for(let t in n)Object.prototype.hasOwnProperty.call(n,t)&&(t=n[t].split("="),e[decodeURIComponent(t[0])]=t[1]&&decodeURIComponent(t[1])||"")}return e},Ze=e=>{let t;return t=e instanceof le?e:le.from(e.toString(),"utf-8"),t.toString("base64")},Ye={operationsSorter:{alpha:(e,t)=>e.get("path").localeCompare(t.get("path")),method:(e,t)=>e.get("method").localeCompare(t.get("method"))},tagsSorter:{alpha:(e,t)=>e.localeCompare(t)}},Qe=e=>{let t=[];for(let n in e){let r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},Xe=(e,t,n)=>!!W()(n,(n=>G()(e[n],t[n])));function et(e){return"string"!=typeof e||""===e?"":(0,L.N)(e)}function tt(e){return!(!e||O()(e).call(e,"localhost")>=0||O()(e).call(e,"127.0.0.1")>=0||"none"===e)}function nt(e){if(!D().OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;const t=T()(e).call(e,((e,t)=>N()(t).call(t,"2")&&d()(e.get("content")||{}).length>0)),n=e.get("default")||D().OrderedMap(),r=(n.get("content")||D().OrderedMap()).keySeq().toJS().length?n:null;return t||r}const rt=e=>"string"==typeof e||e instanceof String?R()(e).call(e).replace(/\s/g,"%20"):"",ot=e=>te()(rt(e).replace(/%20/g,"_")),at=e=>f()(e).call(e,((e,t)=>/^x-/.test(t))),it=e=>f()(e).call(e,((e,t)=>/^pattern|maxLength|minLength|maximum|minimum/.test(t)));function st(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:()=>!0;if("object"!=typeof e||o()(e)||null===e||!t)return e;const a=v()({},e);return c()(n=d()(a)).call(n,(e=>{e===t&&r(a[e],e)?delete a[e]:a[e]=st(a[e],t,r)})),a}function lt(e){if("string"==typeof e)return e;if(e&&e.toJS&&(e=e.toJS()),"object"==typeof e&&null!==e)try{return C()(e,null,2)}catch(t){return String(e)}return null==e?"":e.toString()}function ut(e){return"number"==typeof e?e.toString():e}function ct(e){let{returnAll:t=!1,allowHashes:n=!0}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!D().Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");const r=e.get("name"),o=e.get("in");let a=[];return e&&e.hashCode&&o&&r&&n&&a.push(`${o}.${r}.hash-${e.hashCode()}`),o&&r&&a.push(`${o}.${r}`),a.push(r),t?a:a[0]||""}function pt(e,t){var n;const r=ct(e,{returnAll:!0});return f()(n=i()(r).call(r,(e=>t[e]))).call(n,(e=>void 0!==e))[0]}function ft(){return dt(oe()(32).toString("base64"))}function ht(e){return dt(ie()("sha256").update(e).digest("base64"))}function dt(e){return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}const mt=e=>!e||!(!ce(e)||!e.isEmpty())},2518:(e,t,n)=>{"use strict";function r(e){return function(e){try{return!!JSON.parse(e)}catch(e){return null}}(e)?"json":null}n.d(t,{O:()=>r})},27504:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});const r=function(){var e={location:{},history:{},open:()=>{},close:()=>{},File:function(){}};if("undefined"==typeof window)return e;try{e=window;for(var t of["File","Blob","FormData"])t in window&&(e[t]=window[t])}catch(e){console.error(e)}return e}()},19069:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c});var r=n(14418),o=n.n(r),a=n(58118),i=n.n(a),s=n(43393),l=n.n(s);const u=l().Set.of("type","format","items","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf");function c(e){let{isOAS3:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!l().Map.isMap(e))return{schema:l().Map(),parameterContentMediaType:null};if(!t)return"body"===e.get("in")?{schema:e.get("schema",l().Map()),parameterContentMediaType:null}:{schema:o()(e).call(e,((e,t)=>i()(u).call(u,t))),parameterContentMediaType:null};if(e.get("content")){const t=e.get("content",l().Map({})).keySeq().first();return{schema:e.getIn(["content",t,"schema"],l().Map()),parameterContentMediaType:t}}return{schema:e.get("schema")?e.get("schema",l().Map()):l().Map(),parameterContentMediaType:null}}},60314:(e,t,n)=>{"use strict";n.d(t,{Z:()=>x});var r=n(58309),o=n.n(r),a=n(2250),i=n.n(a),s=n(25110),l=n.n(s),u=n(8712),c=n.n(u),p=n(51679),f=n.n(p),h=n(12373),d=n.n(h),m=n(18492),g=n.n(m),y=n(88306),v=n.n(y);const b=e=>t=>o()(e)&&o()(t)&&e.length===t.length&&i()(e).call(e,((e,n)=>e===t[n])),w=function(){for(var e=arguments.length,t=new Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:w;const{Cache:n}=v();v().Cache=E;const r=v()(e,t);return v().Cache=n,r}},79742:(e,t)=>{"use strict";t.byteLength=function(e){var t=l(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,a=l(e),i=a[0],s=a[1],u=new o(function(e,t,n){return 3*(t+n)/4-n}(0,i,s)),c=0,p=s>0?i-4:i;for(n=0;n>16&255,u[c++]=t>>8&255,u[c++]=255&t;2===s&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,u[c++]=255&t);1===s&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,u[c++]=t>>8&255,u[c++]=255&t);return u},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,a=[],i=16383,s=0,l=r-o;sl?l:s+i));1===o?(t=e[r-1],a.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],a.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return a.join("")};for(var n=[],r=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0,s=a.length;i0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function u(e,t,r){for(var o,a,i=[],s=t;s>18&63]+n[a>>12&63]+n[a>>6&63]+n[63&a]);return i.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},48764:(e,t,n)=>{"use strict";const r=n(79742),o=n(80645),a="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=l,t.SlowBuffer=function(e){+e!=e&&(e=0);return l.alloc(+e)},t.INSPECT_MAX_BYTES=50;const i=2147483647;function s(e){if(e>i)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return p(e)}return u(e,t,n)}function u(e,t,n){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!l.isEncoding(t))throw new TypeError("Unknown encoding: "+t);const n=0|m(e,t);let r=s(n);const o=r.write(e,t);o!==n&&(r=r.slice(0,o));return r}(e,t);if(ArrayBuffer.isView(e))return function(e){if(G(e,Uint8Array)){const t=new Uint8Array(e);return h(t.buffer,t.byteOffset,t.byteLength)}return f(e)}(e);if(null==e)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(G(e,ArrayBuffer)||e&&G(e.buffer,ArrayBuffer))return h(e,t,n);if("undefined"!=typeof SharedArrayBuffer&&(G(e,SharedArrayBuffer)||e&&G(e.buffer,SharedArrayBuffer)))return h(e,t,n);if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type number');const r=e.valueOf&&e.valueOf();if(null!=r&&r!==e)return l.from(r,t,n);const o=function(e){if(l.isBuffer(e)){const t=0|d(e.length),n=s(t);return 0===n.length||e.copy(n,0,0,t),n}if(void 0!==e.length)return"number"!=typeof e.length||Z(e.length)?s(0):f(e);if("Buffer"===e.type&&Array.isArray(e.data))return f(e.data)}(e);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return l.from(e[Symbol.toPrimitive]("string"),t,n);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function c(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function p(e){return c(e),s(e<0?0:0|d(e))}function f(e){const t=e.length<0?0:0|d(e.length),n=s(t);for(let r=0;r=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function m(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||G(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);const n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;let o=!1;for(;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return H(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return J(e).length;default:if(o)return r?-1:H(e).length;t=(""+t).toLowerCase(),o=!0}}function g(e,t,n){let r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return T(this,t,n);case"utf8":case"utf-8":return C(this,t,n);case"ascii":return O(this,t,n);case"latin1":case"binary":return j(this,t,n);case"base64":return A(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return I(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),Z(n=+n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=l.from(t,r)),l.isBuffer(t))return 0===t.length?-1:b(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(e,t,n,r,o){let a,i=1,s=e.length,l=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;i=2,s/=2,l/=2,n/=2}function u(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){let r=-1;for(a=n;as&&(n=s-l),a=n;a>=0;a--){let n=!0;for(let r=0;ro&&(r=o):r=o;const a=t.length;let i;for(r>a/2&&(r=a/2),i=0;i>8,o=n%256,a.push(o),a.push(r);return a}(t,e.length-n),e,n,r)}function A(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function C(e,t,n){n=Math.min(e.length,n);const r=[];let o=t;for(;o239?4:t>223?3:t>191?2:1;if(o+i<=n){let n,r,s,l;switch(i){case 1:t<128&&(a=t);break;case 2:n=e[o+1],128==(192&n)&&(l=(31&t)<<6|63&n,l>127&&(a=l));break;case 3:n=e[o+1],r=e[o+2],128==(192&n)&&128==(192&r)&&(l=(15&t)<<12|(63&n)<<6|63&r,l>2047&&(l<55296||l>57343)&&(a=l));break;case 4:n=e[o+1],r=e[o+2],s=e[o+3],128==(192&n)&&128==(192&r)&&128==(192&s)&&(l=(15&t)<<18|(63&n)<<12|(63&r)<<6|63&s,l>65535&&l<1114112&&(a=l))}}null===a?(a=65533,i=1):a>65535&&(a-=65536,r.push(a>>>10&1023|55296),a=56320|1023&a),r.push(a),o+=i}return function(e){const t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);let n="",r=0;for(;rr.length?(l.isBuffer(t)||(t=l.from(t)),t.copy(r,o)):Uint8Array.prototype.set.call(r,t,o);else{if(!l.isBuffer(t))throw new TypeError('"list" argument must be an Array of Buffers');t.copy(r,o)}o+=t.length}return r},l.byteLength=m,l.prototype._isBuffer=!0,l.prototype.swap16=function(){const e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tn&&(e+=" ... "),""},a&&(l.prototype[a]=l.prototype.inspect),l.prototype.compare=function(e,t,n,r,o){if(G(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;let a=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0);const s=Math.min(a,i),u=this.slice(r,o),c=e.slice(t,n);for(let e=0;e>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}const o=this.length-t;if((void 0===n||n>o)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");let a=!1;for(;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return E(this,e,t,n);case"ascii":case"latin1":case"binary":return x(this,e,t,n);case"base64":return _(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const k=4096;function O(e,t,n){let r="";n=Math.min(e.length,n);for(let o=t;or)&&(n=r);let o="";for(let r=t;rn)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,n,r,o,a){if(!l.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function R(e,t,n,r,o){q(t,r,o,e,n,7);let a=Number(t&BigInt(4294967295));e[n++]=a,a>>=8,e[n++]=a,a>>=8,e[n++]=a,a>>=8,e[n++]=a;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,n}function M(e,t,n,r,o){q(t,r,o,e,n,7);let a=Number(t&BigInt(4294967295));e[n+7]=a,a>>=8,e[n+6]=a,a>>=8,e[n+5]=a,a>>=8,e[n+4]=a;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n+3]=i,i>>=8,e[n+2]=i,i>>=8,e[n+1]=i,i>>=8,e[n]=i,n+8}function D(e,t,n,r,o,a){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function L(e,t,n,r,a){return t=+t,n>>>=0,a||D(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function B(e,t,n,r,a){return t=+t,n>>>=0,a||D(e,0,n,8),o.write(e,t,n,r,52,8),n+8}l.prototype.slice=function(e,t){const n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(t=void 0===t?n:~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),t>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e],o=1,a=0;for(;++a>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e+--t],o=1;for(;t>0&&(o*=256);)r+=this[e+--t]*o;return r},l.prototype.readUint8=l.prototype.readUInt8=function(e,t){return e>>>=0,t||N(e,1,this.length),this[e]},l.prototype.readUint16LE=l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||N(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUint16BE=l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||N(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUint32LE=l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||N(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},l.prototype.readUint32BE=l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||N(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readBigUInt64LE=Q((function(e){$(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t+256*this[++e]+65536*this[++e]+this[++e]*2**24,o=this[++e]+256*this[++e]+65536*this[++e]+n*2**24;return BigInt(r)+(BigInt(o)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t*2**24+65536*this[++e]+256*this[++e]+this[++e],o=this[++e]*2**24+65536*this[++e]+256*this[++e]+n;return(BigInt(r)<>>=0,t>>>=0,n||N(e,t,this.length);let r=this[e],o=1,a=0;for(;++a=o&&(r-=Math.pow(2,8*t)),r},l.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||N(e,t,this.length);let r=t,o=1,a=this[e+--r];for(;r>0&&(o*=256);)a+=this[e+--r]*o;return o*=128,a>=o&&(a-=Math.pow(2,8*t)),a},l.prototype.readInt8=function(e,t){return e>>>=0,t||N(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||N(e,2,this.length);const n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt16BE=function(e,t){e>>>=0,t||N(e,2,this.length);const n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||N(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||N(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readBigInt64LE=Q((function(e){$(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=this[e+4]+256*this[e+5]+65536*this[e+6]+(n<<24);return(BigInt(r)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=(t<<24)+65536*this[++e]+256*this[++e]+this[++e];return(BigInt(r)<>>=0,t||N(e,4,this.length),o.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||N(e,4,this.length),o.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||N(e,8,this.length),o.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||N(e,8,this.length),o.read(this,e,!1,52,8)},l.prototype.writeUintLE=l.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){P(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=1,a=0;for(this[t]=255&e;++a>>=0,n>>>=0,!r){P(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=n-1,a=1;for(this[t+o]=255&e;--o>=0&&(a*=256);)this[t+o]=e/a&255;return t+n},l.prototype.writeUint8=l.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUint16LE=l.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUint16BE=l.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUint32LE=l.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUint32BE=l.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigUInt64LE=Q((function(e,t=0){return R(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeBigUInt64BE=Q((function(e,t=0){return M(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);P(this,e,t,n,r-1,-r)}let o=0,a=1,i=0;for(this[t]=255&e;++o>0)-i&255;return t+n},l.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);P(this,e,t,n,r-1,-r)}let o=n-1,a=1,i=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)e<0&&0===i&&0!==this[t+o+1]&&(i=1),this[t+o]=(e/a>>0)-i&255;return t+n},l.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigInt64LE=Q((function(e,t=0){return R(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeBigInt64BE=Q((function(e,t=0){return M(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeFloatLE=function(e,t,n){return L(this,e,t,!0,n)},l.prototype.writeFloatBE=function(e,t,n){return L(this,e,t,!1,n)},l.prototype.writeDoubleLE=function(e,t,n){return B(this,e,t,!0,n)},l.prototype.writeDoubleBE=function(e,t,n){return B(this,e,t,!1,n)},l.prototype.copy=function(e,t,n,r){if(!l.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o=r+4;n-=3)t=`_${e.slice(n-3,n)}${t}`;return`${e.slice(0,n)}${t}`}function q(e,t,n,r,o,a){if(e>n||e3?0===t||t===BigInt(0)?`>= 0${r} and < 2${r} ** ${8*(a+1)}${r}`:`>= -(2${r} ** ${8*(a+1)-1}${r}) and < 2 ** ${8*(a+1)-1}${r}`:`>= ${t}${r} and <= ${n}${r}`,new F.ERR_OUT_OF_RANGE("value",o,e)}!function(e,t,n){$(t,"offset"),void 0!==e[t]&&void 0!==e[t+n]||V(t,e.length-(n+1))}(r,o,a)}function $(e,t){if("number"!=typeof e)throw new F.ERR_INVALID_ARG_TYPE(t,"number",e)}function V(e,t,n){if(Math.floor(e)!==e)throw $(e,n),new F.ERR_OUT_OF_RANGE(n||"offset","an integer",e);if(t<0)throw new F.ERR_BUFFER_OUT_OF_BOUNDS;throw new F.ERR_OUT_OF_RANGE(n||"offset",`>= ${n?1:0} and <= ${t}`,e)}U("ERR_BUFFER_OUT_OF_BOUNDS",(function(e){return e?`${e} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),U("ERR_INVALID_ARG_TYPE",(function(e,t){return`The "${e}" argument must be of type number. Received type ${typeof t}`}),TypeError),U("ERR_OUT_OF_RANGE",(function(e,t,n){let r=`The value of "${e}" is out of range.`,o=n;return Number.isInteger(n)&&Math.abs(n)>2**32?o=z(String(n)):"bigint"==typeof n&&(o=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(o=z(o)),o+="n"),r+=` It must be ${t}. Received ${o}`,r}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function H(e,t){let n;t=t||1/0;const r=e.length;let o=null;const a=[];for(let i=0;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&a.push(239,191,189);continue}if(i+1===r){(t-=3)>-1&&a.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&a.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&a.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;a.push(n)}else if(n<2048){if((t-=2)<0)break;a.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;a.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;a.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return a}function J(e){return r.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function K(e,t,n,r){let o;for(o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function G(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function Z(e){return e!=e}const Y=function(){const e="0123456789abcdef",t=new Array(256);for(let n=0;n<16;++n){const r=16*n;for(let o=0;o<16;++o)t[r+o]=e[n]+e[o]}return t}();function Q(e){return"undefined"==typeof BigInt?X:e}function X(){throw new Error("BigInt not supported")}},21924:(e,t,n)=>{"use strict";var r=n(40210),o=n(55559),a=o(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&a(e,".prototype.")>-1?o(n):n}},55559:(e,t,n)=>{"use strict";var r=n(58612),o=n(40210),a=o("%Function.prototype.apply%"),i=o("%Function.prototype.call%"),s=o("%Reflect.apply%",!0)||r.call(i,a),l=o("%Object.getOwnPropertyDescriptor%",!0),u=o("%Object.defineProperty%",!0),c=o("%Math.max%");if(u)try{u({},"a",{value:1})}catch(e){u=null}e.exports=function(e){var t=s(r,i,arguments);l&&u&&(l(t,"length").configurable&&u(t,"length",{value:1+c(0,e.length-(arguments.length-1))}));return t};var p=function(){return s(r,a,arguments)};u?u(e.exports,"apply",{value:p}):e.exports.apply=p},94184:(e,t)=>{var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;t{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");var n={},r=(t||{}).decode||o,a=0;for(;a{"use strict";var r=n(11742),o={"text/plain":"Text","text/html":"Url",default:"Text"},a="Copy to clipboard: #{key}, Enter";e.exports=function(e,t){var n,i,s,l,u,c,p=!1;t||(t={}),n=t.debug||!1;try{if(s=r(),l=document.createRange(),u=document.getSelection(),(c=document.createElement("span")).textContent=e,c.ariaHidden="true",c.style.all="unset",c.style.position="fixed",c.style.top=0,c.style.clip="rect(0, 0, 0, 0)",c.style.whiteSpace="pre",c.style.webkitUserSelect="text",c.style.MozUserSelect="text",c.style.msUserSelect="text",c.style.userSelect="text",c.addEventListener("copy",(function(r){if(r.stopPropagation(),t.format)if(r.preventDefault(),void 0===r.clipboardData){n&&console.warn("unable to use e.clipboardData"),n&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var a=o[t.format]||o.default;window.clipboardData.setData(a,e)}else r.clipboardData.clearData(),r.clipboardData.setData(t.format,e);t.onCopy&&(r.preventDefault(),t.onCopy(r.clipboardData))})),document.body.appendChild(c),l.selectNodeContents(c),u.addRange(l),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");p=!0}catch(r){n&&console.error("unable to copy using execCommand: ",r),n&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(t.format||"text",e),t.onCopy&&t.onCopy(window.clipboardData),p=!0}catch(r){n&&console.error("unable to copy using clipboardData: ",r),n&&console.error("falling back to prompt"),i=function(e){var t=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return e.replace(/#{\s*key\s*}/g,t)}("message"in t?t.message:a),window.prompt(i,e)}}finally{u&&("function"==typeof u.removeRange?u.removeRange(l):u.removeAllRanges()),c&&document.body.removeChild(c),s()}return p}},90093:(e,t,n)=>{var r=n(28196);e.exports=r},3688:(e,t,n)=>{var r=n(11955);e.exports=r},83838:(e,t,n)=>{var r=n(46279);e.exports=r},15684:(e,t,n)=>{var r=n(19373);e.exports=r},65362:(e,t,n)=>{var r=n(63383);e.exports=r},91254:(e,t,n)=>{var r=n(57396);e.exports=r},43536:(e,t,n)=>{var r=n(41910);e.exports=r},37331:(e,t,n)=>{var r=n(79427);e.exports=r},68522:(e,t,n)=>{var r=n(62857);e.exports=r},73151:(e,t,n)=>{var r=n(9534);e.exports=r},45012:(e,t,n)=>{var r=n(23059);e.exports=r},80281:(e,t,n)=>{var r=n(92547);n(43975),e.exports=r},40031:(e,t,n)=>{var r=n(46509);e.exports=r},17487:(e,t,n)=>{var r=n(35774);e.exports=r},54493:(e,t,n)=>{n(77971),n(53242);var r=n(54058);e.exports=r.Array.from},24034:(e,t,n)=>{n(92737);var r=n(54058);e.exports=r.Array.isArray},15367:(e,t,n)=>{n(85906);var r=n(35703);e.exports=r("Array").concat},12710:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").entries},51459:(e,t,n)=>{n(48851);var r=n(35703);e.exports=r("Array").every},6172:(e,t,n)=>{n(80290);var r=n(35703);e.exports=r("Array").fill},62383:(e,t,n)=>{n(21501);var r=n(35703);e.exports=r("Array").filter},60009:(e,t,n)=>{n(44929);var r=n(35703);e.exports=r("Array").findIndex},17671:(e,t,n)=>{n(80833);var r=n(35703);e.exports=r("Array").find},99324:(e,t,n)=>{n(2437);var r=n(35703);e.exports=r("Array").forEach},80991:(e,t,n)=>{n(97690);var r=n(35703);e.exports=r("Array").includes},8700:(e,t,n)=>{n(99076);var r=n(35703);e.exports=r("Array").indexOf},95909:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").keys},6442:(e,t,n)=>{n(75915);var r=n(35703);e.exports=r("Array").lastIndexOf},23866:(e,t,n)=>{n(68787);var r=n(35703);e.exports=r("Array").map},52999:(e,t,n)=>{n(81876);var r=n(35703);e.exports=r("Array").reduce},24900:(e,t,n)=>{n(60186);var r=n(35703);e.exports=r("Array").slice},3824:(e,t,n)=>{n(36026);var r=n(35703);e.exports=r("Array").some},2948:(e,t,n)=>{n(4115);var r=n(35703);e.exports=r("Array").sort},78209:(e,t,n)=>{n(98611);var r=n(35703);e.exports=r("Array").splice},14423:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").values},81103:(e,t,n)=>{n(95160);var r=n(54058);e.exports=r.Date.now},27700:(e,t,n)=>{n(73381);var r=n(35703);e.exports=r("Function").bind},16246:(e,t,n)=>{var r=n(7046),o=n(27700),a=Function.prototype;e.exports=function(e){var t=e.bind;return e===a||r(a,e)&&t===a.bind?o:t}},56043:(e,t,n)=>{var r=n(7046),o=n(15367),a=Array.prototype;e.exports=function(e){var t=e.concat;return e===a||r(a,e)&&t===a.concat?o:t}},13160:(e,t,n)=>{var r=n(7046),o=n(51459),a=Array.prototype;e.exports=function(e){var t=e.every;return e===a||r(a,e)&&t===a.every?o:t}},80446:(e,t,n)=>{var r=n(7046),o=n(6172),a=Array.prototype;e.exports=function(e){var t=e.fill;return e===a||r(a,e)&&t===a.fill?o:t}},2480:(e,t,n)=>{var r=n(7046),o=n(62383),a=Array.prototype;e.exports=function(e){var t=e.filter;return e===a||r(a,e)&&t===a.filter?o:t}},7147:(e,t,n)=>{var r=n(7046),o=n(60009),a=Array.prototype;e.exports=function(e){var t=e.findIndex;return e===a||r(a,e)&&t===a.findIndex?o:t}},32236:(e,t,n)=>{var r=n(7046),o=n(17671),a=Array.prototype;e.exports=function(e){var t=e.find;return e===a||r(a,e)&&t===a.find?o:t}},58557:(e,t,n)=>{var r=n(7046),o=n(80991),a=n(21631),i=Array.prototype,s=String.prototype;e.exports=function(e){var t=e.includes;return e===i||r(i,e)&&t===i.includes?o:"string"==typeof e||e===s||r(s,e)&&t===s.includes?a:t}},34570:(e,t,n)=>{var r=n(7046),o=n(8700),a=Array.prototype;e.exports=function(e){var t=e.indexOf;return e===a||r(a,e)&&t===a.indexOf?o:t}},57564:(e,t,n)=>{var r=n(7046),o=n(6442),a=Array.prototype;e.exports=function(e){var t=e.lastIndexOf;return e===a||r(a,e)&&t===a.lastIndexOf?o:t}},88287:(e,t,n)=>{var r=n(7046),o=n(23866),a=Array.prototype;e.exports=function(e){var t=e.map;return e===a||r(a,e)&&t===a.map?o:t}},68025:(e,t,n)=>{var r=n(7046),o=n(52999),a=Array.prototype;e.exports=function(e){var t=e.reduce;return e===a||r(a,e)&&t===a.reduce?o:t}},59257:(e,t,n)=>{var r=n(7046),o=n(80454),a=String.prototype;e.exports=function(e){var t=e.repeat;return"string"==typeof e||e===a||r(a,e)&&t===a.repeat?o:t}},69601:(e,t,n)=>{var r=n(7046),o=n(24900),a=Array.prototype;e.exports=function(e){var t=e.slice;return e===a||r(a,e)&&t===a.slice?o:t}},28299:(e,t,n)=>{var r=n(7046),o=n(3824),a=Array.prototype;e.exports=function(e){var t=e.some;return e===a||r(a,e)&&t===a.some?o:t}},69355:(e,t,n)=>{var r=n(7046),o=n(2948),a=Array.prototype;e.exports=function(e){var t=e.sort;return e===a||r(a,e)&&t===a.sort?o:t}},18339:(e,t,n)=>{var r=n(7046),o=n(78209),a=Array.prototype;e.exports=function(e){var t=e.splice;return e===a||r(a,e)&&t===a.splice?o:t}},71611:(e,t,n)=>{var r=n(7046),o=n(3269),a=String.prototype;e.exports=function(e){var t=e.startsWith;return"string"==typeof e||e===a||r(a,e)&&t===a.startsWith?o:t}},62774:(e,t,n)=>{var r=n(7046),o=n(13348),a=String.prototype;e.exports=function(e){var t=e.trim;return"string"==typeof e||e===a||r(a,e)&&t===a.trim?o:t}},84426:(e,t,n)=>{n(32619);var r=n(54058),o=n(79730);r.JSON||(r.JSON={stringify:JSON.stringify}),e.exports=function(e,t,n){return o(r.JSON.stringify,null,arguments)}},91018:(e,t,n)=>{n(66274),n(37501),n(55967),n(77971);var r=n(54058);e.exports=r.Map},45999:(e,t,n)=>{n(49221);var r=n(54058);e.exports=r.Object.assign},7702:(e,t,n)=>{n(74979);var r=n(54058).Object,o=e.exports=function(e,t){return r.defineProperties(e,t)};r.defineProperties.sham&&(o.sham=!0)},48171:(e,t,n)=>{n(86450);var r=n(54058).Object,o=e.exports=function(e,t,n){return r.defineProperty(e,t,n)};r.defineProperty.sham&&(o.sham=!0)},286:(e,t,n)=>{n(46924);var r=n(54058).Object,o=e.exports=function(e,t){return r.getOwnPropertyDescriptor(e,t)};r.getOwnPropertyDescriptor.sham&&(o.sham=!0)},92766:(e,t,n)=>{n(88482);var r=n(54058);e.exports=r.Object.getOwnPropertyDescriptors},30498:(e,t,n)=>{n(35824);var r=n(54058);e.exports=r.Object.getOwnPropertySymbols},48494:(e,t,n)=>{n(21724);var r=n(54058);e.exports=r.Object.keys},98430:(e,t,n)=>{n(26614);var r=n(54058);e.exports=r.Object.values},52956:(e,t,n)=>{n(47627),n(66274),n(55967),n(98881),n(4560),n(91302),n(44349),n(77971);var r=n(54058);e.exports=r.Promise},21631:(e,t,n)=>{n(11035);var r=n(35703);e.exports=r("String").includes},80454:(e,t,n)=>{n(60986);var r=n(35703);e.exports=r("String").repeat},3269:(e,t,n)=>{n(94761);var r=n(35703);e.exports=r("String").startsWith},13348:(e,t,n)=>{n(57398);var r=n(35703);e.exports=r("String").trim},57473:(e,t,n)=>{n(85906),n(55967),n(35824),n(8555),n(52615),n(21732),n(35903),n(1825),n(28394),n(45915),n(61766),n(62737),n(89911),n(74315),n(63131),n(64714),n(70659),n(69120),n(79413),n(1502);var r=n(54058);e.exports=r.Symbol},24227:(e,t,n)=>{n(66274),n(55967),n(77971),n(1825);var r=n(11477);e.exports=r.f("iterator")},62978:(e,t,n)=>{n(18084),n(63131);var r=n(11477);e.exports=r.f("toPrimitive")},14122:(e,t,n)=>{e.exports=n(89097)},44442:(e,t,n)=>{e.exports=n(51675)},57152:(e,t,n)=>{e.exports=n(82507)},69447:(e,t,n)=>{e.exports=n(628)},60269:(e,t,n)=>{e.exports=n(76936)},70573:(e,t,n)=>{e.exports=n(18180)},73685:(e,t,n)=>{e.exports=n(80621)},27533:(e,t,n)=>{e.exports=n(22948)},39057:(e,t,n)=>{e.exports=n(82108)},84710:(e,t,n)=>{e.exports=n(14058)},93799:(e,t,n)=>{e.exports=n(92093)},86600:(e,t,n)=>{e.exports=n(52201)},9759:(e,t,n)=>{e.exports=n(27398)},71384:(e,t,n)=>{e.exports=n(26189)},89097:(e,t,n)=>{var r=n(90093);e.exports=r},51675:(e,t,n)=>{var r=n(3688);e.exports=r},82507:(e,t,n)=>{var r=n(83838);e.exports=r},628:(e,t,n)=>{var r=n(15684);e.exports=r},76936:(e,t,n)=>{var r=n(65362);e.exports=r},18180:(e,t,n)=>{var r=n(91254);e.exports=r},80621:(e,t,n)=>{var r=n(43536);e.exports=r},22948:(e,t,n)=>{var r=n(37331);e.exports=r},82108:(e,t,n)=>{var r=n(68522);e.exports=r},14058:(e,t,n)=>{var r=n(73151);e.exports=r},92093:(e,t,n)=>{var r=n(45012);e.exports=r},52201:(e,t,n)=>{var r=n(80281);n(28783),n(22731),n(85605),n(65799),n(31943),n(46774),n(45414),n(80620),n(36172),e.exports=r},27398:(e,t,n)=>{var r=n(40031);e.exports=r},26189:(e,t,n)=>{var r=n(17487);e.exports=r},24883:(e,t,n)=>{var r=n(57475),o=n(69826),a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not a function")}},174:(e,t,n)=>{var r=n(24284),o=n(69826),a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not a constructor")}},11851:(e,t,n)=>{var r=n(57475),o=String,a=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw a("Can't set "+o(e)+" as a prototype")}},18479:e=>{e.exports=function(){}},5743:(e,t,n)=>{var r=n(7046),o=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw o("Incorrect invocation")}},96059:(e,t,n)=>{var r=n(10941),o=String,a=TypeError;e.exports=function(e){if(r(e))return e;throw a(o(e)+" is not an object")}},97135:(e,t,n)=>{var r=n(95981);e.exports=r((function(){if("function"==typeof ArrayBuffer){var e=new ArrayBuffer(8);Object.isExtensible(e)&&Object.defineProperty(e,"a",{value:8})}}))},91860:(e,t,n)=>{"use strict";var r=n(89678),o=n(59413),a=n(10623);e.exports=function(e){for(var t=r(this),n=a(t),i=arguments.length,s=o(i>1?arguments[1]:void 0,n),l=i>2?arguments[2]:void 0,u=void 0===l?n:o(l,n);u>s;)t[s++]=e;return t}},56837:(e,t,n)=>{"use strict";var r=n(3610).forEach,o=n(34194)("forEach");e.exports=o?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},11354:(e,t,n)=>{"use strict";var r=n(86843),o=n(78834),a=n(89678),i=n(75196),s=n(6782),l=n(24284),u=n(10623),c=n(55449),p=n(53476),f=n(22902),h=Array;e.exports=function(e){var t=a(e),n=l(this),d=arguments.length,m=d>1?arguments[1]:void 0,g=void 0!==m;g&&(m=r(m,d>2?arguments[2]:void 0));var y,v,b,w,E,x,_=f(t),S=0;if(!_||this===h&&s(_))for(y=u(t),v=n?new this(y):h(y);y>S;S++)x=g?m(t[S],S):t[S],c(v,S,x);else for(E=(w=p(t,_)).next,v=n?new this:[];!(b=o(E,w)).done;S++)x=g?i(w,m,[b.value,S],!0):b.value,c(v,S,x);return v.length=S,v}},31692:(e,t,n)=>{var r=n(74529),o=n(59413),a=n(10623),i=function(e){return function(t,n,i){var s,l=r(t),u=a(l),c=o(i,u);if(e&&n!=n){for(;u>c;)if((s=l[c++])!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===n)return e||c||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},3610:(e,t,n)=>{var r=n(86843),o=n(95329),a=n(37026),i=n(89678),s=n(10623),l=n(64692),u=o([].push),c=function(e){var t=1==e,n=2==e,o=3==e,c=4==e,p=6==e,f=7==e,h=5==e||p;return function(d,m,g,y){for(var v,b,w=i(d),E=a(w),x=r(m,g),_=s(E),S=0,A=y||l,C=t?A(d,_):n||f?A(d,0):void 0;_>S;S++)if((h||S in E)&&(b=x(v=E[S],S,w),e))if(t)C[S]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return S;case 2:u(C,v)}else switch(e){case 4:return!1;case 7:u(C,v)}return p?-1:o||c?c:C}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterReject:c(7)}},67145:(e,t,n)=>{"use strict";var r=n(79730),o=n(74529),a=n(62435),i=n(10623),s=n(34194),l=Math.min,u=[].lastIndexOf,c=!!u&&1/[1].lastIndexOf(1,-0)<0,p=s("lastIndexOf"),f=c||!p;e.exports=f?function(e){if(c)return r(u,this,arguments)||0;var t=o(this),n=i(t),s=n-1;for(arguments.length>1&&(s=l(s,a(arguments[1]))),s<0&&(s=n+s);s>=0;s--)if(s in t&&t[s]===e)return s||0;return-1}:u},50568:(e,t,n)=>{var r=n(95981),o=n(99813),a=n(53385),i=o("species");e.exports=function(e){return a>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},34194:(e,t,n)=>{"use strict";var r=n(95981);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},46499:(e,t,n)=>{var r=n(24883),o=n(89678),a=n(37026),i=n(10623),s=TypeError,l=function(e){return function(t,n,l,u){r(n);var c=o(t),p=a(c),f=i(c),h=e?f-1:0,d=e?-1:1;if(l<2)for(;;){if(h in p){u=p[h],h+=d;break}if(h+=d,e?h<0:f<=h)throw s("Reduce of empty array with no initial value")}for(;e?h>=0:f>h;h+=d)h in p&&(u=n(u,p[h],h,c));return u}};e.exports={left:l(!1),right:l(!0)}},89779:(e,t,n)=>{"use strict";var r=n(55746),o=n(1052),a=TypeError,i=Object.getOwnPropertyDescriptor,s=r&&!function(){if(void 0!==this)return!0;try{Object.defineProperty([],"length",{writable:!1}).length=1}catch(e){return e instanceof TypeError}}();e.exports=s?function(e,t){if(o(e)&&!i(e,"length").writable)throw a("Cannot set read only .length");return e.length=t}:function(e,t){return e.length=t}},15790:(e,t,n)=>{var r=n(59413),o=n(10623),a=n(55449),i=Array,s=Math.max;e.exports=function(e,t,n){for(var l=o(e),u=r(t,l),c=r(void 0===n?l:n,l),p=i(s(c-u,0)),f=0;u{var r=n(95329);e.exports=r([].slice)},61388:(e,t,n)=>{var r=n(15790),o=Math.floor,a=function(e,t){var n=e.length,l=o(n/2);return n<8?i(e,t):s(e,a(r(e,0,l),t),a(r(e,l),t),t)},i=function(e,t){for(var n,r,o=e.length,a=1;a0;)e[r]=e[--r];r!==a++&&(e[r]=n)}return e},s=function(e,t,n,r){for(var o=t.length,a=n.length,i=0,s=0;i{var r=n(1052),o=n(24284),a=n(10941),i=n(99813)("species"),s=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(o(t)&&(t===s||r(t.prototype))||a(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?s:t}},64692:(e,t,n)=>{var r=n(5693);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},75196:(e,t,n)=>{var r=n(96059),o=n(7609);e.exports=function(e,t,n,a){try{return a?t(r(n)[0],n[1]):t(n)}catch(t){o(e,"throw",t)}}},21385:(e,t,n)=>{var r=n(99813)("iterator"),o=!1;try{var a=0,i={next:function(){return{done:!!a++}},return:function(){o=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var a={};a[r]=function(){return{next:function(){return{done:n=!0}}}},e(a)}catch(e){}return n}},82532:(e,t,n)=>{var r=n(95329),o=r({}.toString),a=r("".slice);e.exports=function(e){return a(o(e),8,-1)}},9697:(e,t,n)=>{var r=n(22885),o=n(57475),a=n(82532),i=n(99813)("toStringTag"),s=Object,l="Arguments"==a(function(){return arguments}());e.exports=r?a:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=s(e),i))?n:l?a(t):"Object"==(r=a(t))&&o(t.callee)?"Arguments":r}},85616:(e,t,n)=>{"use strict";var r=n(29290),o=n(29202),a=n(94380),i=n(86843),s=n(5743),l=n(82119),u=n(93091),c=n(75105),p=n(23538),f=n(94431),h=n(55746),d=n(21647).fastKey,m=n(45402),g=m.set,y=m.getterFor;e.exports={getConstructor:function(e,t,n,c){var p=e((function(e,o){s(e,f),g(e,{type:t,index:r(null),first:void 0,last:void 0,size:0}),h||(e.size=0),l(o)||u(o,e[c],{that:e,AS_ENTRIES:n})})),f=p.prototype,m=y(t),v=function(e,t,n){var r,o,a=m(e),i=b(e,t);return i?i.value=n:(a.last=i={index:o=d(t,!0),key:t,value:n,previous:r=a.last,next:void 0,removed:!1},a.first||(a.first=i),r&&(r.next=i),h?a.size++:e.size++,"F"!==o&&(a.index[o]=i)),e},b=function(e,t){var n,r=m(e),o=d(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return a(f,{clear:function(){for(var e=m(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,h?e.size=0:this.size=0},delete:function(e){var t=this,n=m(t),r=b(t,e);if(r){var o=r.next,a=r.previous;delete n.index[r.index],r.removed=!0,a&&(a.next=o),o&&(o.previous=a),n.first==r&&(n.first=o),n.last==r&&(n.last=a),h?n.size--:t.size--}return!!r},forEach:function(e){for(var t,n=m(this),r=i(e,arguments.length>1?arguments[1]:void 0);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!b(this,e)}}),a(f,n?{get:function(e){var t=b(this,e);return t&&t.value},set:function(e,t){return v(this,0===e?0:e,t)}}:{add:function(e){return v(this,e=0===e?0:e,e)}}),h&&o(f,"size",{configurable:!0,get:function(){return m(this).size}}),p},setStrong:function(e,t,n){var r=t+" Iterator",o=y(t),a=y(r);c(e,t,(function(e,t){g(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=a(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?p("keys"==t?n.key:"values"==t?n.value:[n.key,n.value],!1):(e.target=void 0,p(void 0,!0))}),n?"entries":"values",!n,!0),f(t)}}},24683:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),a=n(21647),i=n(95981),s=n(32029),l=n(93091),u=n(5743),c=n(57475),p=n(10941),f=n(90904),h=n(65988).f,d=n(3610).forEach,m=n(55746),g=n(45402),y=g.set,v=g.getterFor;e.exports=function(e,t,n){var g,b=-1!==e.indexOf("Map"),w=-1!==e.indexOf("Weak"),E=b?"set":"add",x=o[e],_=x&&x.prototype,S={};if(m&&c(x)&&(w||_.forEach&&!i((function(){(new x).entries().next()})))){var A=(g=t((function(t,n){y(u(t,A),{type:e,collection:new x}),null!=n&&l(n,t[E],{that:t,AS_ENTRIES:b})}))).prototype,C=v(e);d(["add","clear","delete","forEach","get","has","set","keys","values","entries"],(function(e){var t="add"==e||"set"==e;!(e in _)||w&&"clear"==e||s(A,e,(function(n,r){var o=C(this).collection;if(!t&&w&&!p(n))return"get"==e&&void 0;var a=o[e](0===n?0:n,r);return t?this:a}))})),w||h(A,"size",{configurable:!0,get:function(){return C(this).collection.size}})}else g=n.getConstructor(t,e,b,E),a.enable();return f(g,e,!1,!0),S[e]=g,r({global:!0,forced:!0},S),w||n.setStrong(g,e,b),g}},23489:(e,t,n)=>{var r=n(90953),o=n(31136),a=n(49677),i=n(65988);e.exports=function(e,t,n){for(var s=o(t),l=i.f,u=a.f,c=0;c{var r=n(99813)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},64160:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},23538:e=>{e.exports=function(e,t){return{value:e,done:t}}},32029:(e,t,n)=>{var r=n(55746),o=n(65988),a=n(31887);e.exports=r?function(e,t,n){return o.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},31887:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},55449:(e,t,n)=>{"use strict";var r=n(83894),o=n(65988),a=n(31887);e.exports=function(e,t,n){var i=r(t);i in e?o.f(e,i,a(0,n)):e[i]=n}},29202:(e,t,n)=>{var r=n(65988);e.exports=function(e,t,n){return r.f(e,t,n)}},95929:(e,t,n)=>{var r=n(32029);e.exports=function(e,t,n,o){return o&&o.enumerable?e[t]=n:r(e,t,n),e}},94380:(e,t,n)=>{var r=n(95929);e.exports=function(e,t,n){for(var o in t)n&&n.unsafe&&e[o]?e[o]=t[o]:r(e,o,t[o],n);return e}},75609:(e,t,n)=>{var r=n(21899),o=Object.defineProperty;e.exports=function(e,t){try{o(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},15863:(e,t,n)=>{"use strict";var r=n(69826),o=TypeError;e.exports=function(e,t){if(!delete e[t])throw o("Cannot delete property "+r(t)+" of "+r(e))}},55746:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},61333:(e,t,n)=>{var r=n(21899),o=n(10941),a=r.document,i=o(a)&&o(a.createElement);e.exports=function(e){return i?a.createElement(e):{}}},66796:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},63281:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},34342:(e,t,n)=>{var r=n(2861).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},23321:(e,t,n)=>{var r=n(48501),o=n(6049);e.exports=!r&&!o&&"object"==typeof window&&"object"==typeof document},56491:e=>{e.exports="function"==typeof Bun&&Bun&&"string"==typeof Bun.version},48501:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},81046:(e,t,n)=>{var r=n(2861);e.exports=/MSIE|Trident/.test(r)},4470:(e,t,n)=>{var r=n(2861);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},22749:(e,t,n)=>{var r=n(2861);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},6049:(e,t,n)=>{var r=n(34155),o=n(82532);e.exports=void 0!==r&&"process"==o(r)},58045:(e,t,n)=>{var r=n(2861);e.exports=/web0s(?!.*chrome)/i.test(r)},2861:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(e,t,n)=>{var r,o,a=n(21899),i=n(2861),s=a.process,l=a.Deno,u=s&&s.versions||l&&l.version,c=u&&u.v8;c&&(o=(r=c.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!o&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(o=+r[1]),e.exports=o},18938:(e,t,n)=>{var r=n(2861).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},35703:(e,t,n)=>{var r=n(54058);e.exports=function(e){return r[e+"Prototype"]}},56759:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(e,t,n)=>{var r=n(95329),o=Error,a=r("".replace),i=String(o("zxcasd").stack),s=/\n\s*at [^:]*:[^\n]*/,l=s.test(i);e.exports=function(e,t){if(l&&"string"==typeof e&&!o.prepareStackTrace)for(;t--;)e=a(e,s,"");return e}},79585:(e,t,n)=>{var r=n(32029),o=n(53995),a=n(18780),i=Error.captureStackTrace;e.exports=function(e,t,n,s){a&&(i?i(e,t):r(e,"stack",o(n,s)))}},18780:(e,t,n)=>{var r=n(95981),o=n(31887);e.exports=!r((function(){var e=Error("a");return!("stack"in e)||(Object.defineProperty(e,"stack",o(1,7)),7!==e.stack)}))},76887:(e,t,n)=>{"use strict";var r=n(21899),o=n(79730),a=n(97484),i=n(57475),s=n(49677).f,l=n(37252),u=n(54058),c=n(86843),p=n(32029),f=n(90953),h=function(e){var t=function(n,r,a){if(this instanceof t){switch(arguments.length){case 0:return new e;case 1:return new e(n);case 2:return new e(n,r)}return new e(n,r,a)}return o(e,this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,o,d,m,g,y,v,b,w,E=e.target,x=e.global,_=e.stat,S=e.proto,A=x?r:_?r[E]:(r[E]||{}).prototype,C=x?u:u[E]||p(u,E,{})[E],k=C.prototype;for(m in t)o=!(n=l(x?m:E+(_?".":"#")+m,e.forced))&&A&&f(A,m),y=C[m],o&&(v=e.dontCallGetSet?(w=s(A,m))&&w.value:A[m]),g=o&&v?v:t[m],o&&typeof y==typeof g||(b=e.bind&&o?c(g,r):e.wrap&&o?h(g):S&&i(g)?a(g):g,(e.sham||g&&g.sham||y&&y.sham)&&p(b,"sham",!0),p(C,m,b),S&&(f(u,d=E+"Prototype")||p(u,d,{}),p(u[d],m,g),e.real&&k&&(n||!k[m])&&p(k,m,g)))}},95981:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},45602:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},79730:(e,t,n)=>{var r=n(18285),o=Function.prototype,a=o.apply,i=o.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(a):function(){return i.apply(a,arguments)})},86843:(e,t,n)=>{var r=n(97484),o=n(24883),a=n(18285),i=r(r.bind);e.exports=function(e,t){return o(e),void 0===t?e:a?i(e,t):function(){return e.apply(t,arguments)}}},18285:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},98308:(e,t,n)=>{"use strict";var r=n(95329),o=n(24883),a=n(10941),i=n(90953),s=n(93765),l=n(18285),u=Function,c=r([].concat),p=r([].join),f={},h=function(e,t,n){if(!i(f,t)){for(var r=[],o=0;o{var r=n(18285),o=Function.prototype.call;e.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},79417:(e,t,n)=>{var r=n(55746),o=n(90953),a=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,s=o(a,"name"),l=s&&"something"===function(){}.name,u=s&&(!r||r&&i(a,"name").configurable);e.exports={EXISTS:s,PROPER:l,CONFIGURABLE:u}},45526:(e,t,n)=>{var r=n(95329),o=n(24883);e.exports=function(e,t,n){try{return r(o(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},97484:(e,t,n)=>{var r=n(82532),o=n(95329);e.exports=function(e){if("Function"===r(e))return o(e)}},95329:(e,t,n)=>{var r=n(18285),o=Function.prototype,a=o.call,i=r&&o.bind.bind(a,a);e.exports=r?i:function(e){return function(){return a.apply(e,arguments)}}},626:(e,t,n)=>{var r=n(54058),o=n(21899),a=n(57475),i=function(e){return a(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},22902:(e,t,n)=>{var r=n(9697),o=n(14229),a=n(82119),i=n(12077),s=n(99813)("iterator");e.exports=function(e){if(!a(e))return o(e,s)||o(e,"@@iterator")||i[r(e)]}},53476:(e,t,n)=>{var r=n(78834),o=n(24883),a=n(96059),i=n(69826),s=n(22902),l=TypeError;e.exports=function(e,t){var n=arguments.length<2?s(e):t;if(o(n))return a(r(n,e));throw l(i(e)+" is not iterable")}},33323:(e,t,n)=>{var r=n(95329),o=n(1052),a=n(57475),i=n(82532),s=n(85803),l=r([].push);e.exports=function(e){if(a(e))return e;if(o(e)){for(var t=e.length,n=[],r=0;r{var r=n(24883),o=n(82119);e.exports=function(e,t){var n=e[t];return o(n)?void 0:r(n)}},21899:(e,t,n)=>{var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},90953:(e,t,n)=>{var r=n(95329),o=n(89678),a=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return a(o(e),t)}},27748:e=>{e.exports={}},34845:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},15463:(e,t,n)=>{var r=n(626);e.exports=r("document","documentElement")},2840:(e,t,n)=>{var r=n(55746),o=n(95981),a=n(61333);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(a("div"),"a",{get:function(){return 7}}).a}))},37026:(e,t,n)=>{var r=n(95329),o=n(95981),a=n(82532),i=Object,s=r("".split);e.exports=o((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==a(e)?s(e,""):i(e)}:i},81302:(e,t,n)=>{var r=n(95329),o=n(57475),a=n(63030),i=r(Function.toString);o(a.inspectSource)||(a.inspectSource=function(e){return i(e)}),e.exports=a.inspectSource},53794:(e,t,n)=>{var r=n(10941),o=n(32029);e.exports=function(e,t){r(t)&&"cause"in t&&o(e,"cause",t.cause)}},21647:(e,t,n)=>{var r=n(76887),o=n(95329),a=n(27748),i=n(10941),s=n(90953),l=n(65988).f,u=n(10946),c=n(684),p=n(91584),f=n(99418),h=n(45602),d=!1,m=f("meta"),g=0,y=function(e){l(e,m,{value:{objectID:"O"+g++,weakData:{}}})},v=e.exports={enable:function(){v.enable=function(){},d=!0;var e=u.f,t=o([].splice),n={};n[m]=1,e(n).length&&(u.f=function(n){for(var r=e(n),o=0,a=r.length;o{var r,o,a,i=n(47093),s=n(21899),l=n(10941),u=n(32029),c=n(90953),p=n(63030),f=n(44262),h=n(27748),d="Object already initialized",m=s.TypeError,g=s.WeakMap;if(i||p.state){var y=p.state||(p.state=new g);y.get=y.get,y.has=y.has,y.set=y.set,r=function(e,t){if(y.has(e))throw m(d);return t.facade=e,y.set(e,t),t},o=function(e){return y.get(e)||{}},a=function(e){return y.has(e)}}else{var v=f("state");h[v]=!0,r=function(e,t){if(c(e,v))throw m(d);return t.facade=e,u(e,v,t),t},o=function(e){return c(e,v)?e[v]:{}},a=function(e){return c(e,v)}}e.exports={set:r,get:o,has:a,enforce:function(e){return a(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw m("Incompatible receiver, "+e+" required");return n}}}},6782:(e,t,n)=>{var r=n(99813),o=n(12077),a=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||i[a]===e)}},1052:(e,t,n)=>{var r=n(82532);e.exports=Array.isArray||function(e){return"Array"==r(e)}},57475:(e,t,n)=>{var r=n(76616),o=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===o}:function(e){return"function"==typeof e}},24284:(e,t,n)=>{var r=n(95329),o=n(95981),a=n(57475),i=n(9697),s=n(626),l=n(81302),u=function(){},c=[],p=s("Reflect","construct"),f=/^\s*(?:class|function)\b/,h=r(f.exec),d=!f.exec(u),m=function(e){if(!a(e))return!1;try{return p(u,c,e),!0}catch(e){return!1}},g=function(e){if(!a(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return d||!!h(f,l(e))}catch(e){return!0}};g.sham=!0,e.exports=!p||o((function(){var e;return m(m.call)||!m(Object)||!m((function(){e=!0}))||e}))?g:m},37252:(e,t,n)=>{var r=n(95981),o=n(57475),a=/#|\.prototype\./,i=function(e,t){var n=l[s(e)];return n==c||n!=u&&(o(t)?r(t):!!t)},s=i.normalize=function(e){return String(e).replace(a,".").toLowerCase()},l=i.data={},u=i.NATIVE="N",c=i.POLYFILL="P";e.exports=i},82119:e=>{e.exports=function(e){return null==e}},10941:(e,t,n)=>{var r=n(57475),o=n(76616),a=o.all;e.exports=o.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===a}:function(e){return"object"==typeof e?null!==e:r(e)}},82529:e=>{e.exports=!0},60685:(e,t,n)=>{var r=n(10941),o=n(82532),a=n(99813)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[a])?!!t:"RegExp"==o(e))}},56664:(e,t,n)=>{var r=n(626),o=n(57475),a=n(7046),i=n(32302),s=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return o(t)&&a(t.prototype,s(e))}},93091:(e,t,n)=>{var r=n(86843),o=n(78834),a=n(96059),i=n(69826),s=n(6782),l=n(10623),u=n(7046),c=n(53476),p=n(22902),f=n(7609),h=TypeError,d=function(e,t){this.stopped=e,this.result=t},m=d.prototype;e.exports=function(e,t,n){var g,y,v,b,w,E,x,_=n&&n.that,S=!(!n||!n.AS_ENTRIES),A=!(!n||!n.IS_RECORD),C=!(!n||!n.IS_ITERATOR),k=!(!n||!n.INTERRUPTED),O=r(t,_),j=function(e){return g&&f(g,"normal",e),new d(!0,e)},T=function(e){return S?(a(e),k?O(e[0],e[1],j):O(e[0],e[1])):k?O(e,j):O(e)};if(A)g=e.iterator;else if(C)g=e;else{if(!(y=p(e)))throw h(i(e)+" is not iterable");if(s(y)){for(v=0,b=l(e);b>v;v++)if((w=T(e[v]))&&u(m,w))return w;return new d(!1)}g=c(e,y)}for(E=A?e.next:g.next;!(x=o(E,g)).done;){try{w=T(x.value)}catch(e){f(g,"throw",e)}if("object"==typeof w&&w&&u(m,w))return w}return new d(!1)}},7609:(e,t,n)=>{var r=n(78834),o=n(96059),a=n(14229);e.exports=function(e,t,n){var i,s;o(e);try{if(!(i=a(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){s=!0,i=e}if("throw"===t)throw n;if(s)throw i;return o(i),n}},53847:(e,t,n)=>{"use strict";var r=n(35143).IteratorPrototype,o=n(29290),a=n(31887),i=n(90904),s=n(12077),l=function(){return this};e.exports=function(e,t,n,u){var c=t+" Iterator";return e.prototype=o(r,{next:a(+!u,n)}),i(e,c,!1,!0),s[c]=l,e}},75105:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(82529),i=n(79417),s=n(57475),l=n(53847),u=n(249),c=n(88929),p=n(90904),f=n(32029),h=n(95929),d=n(99813),m=n(12077),g=n(35143),y=i.PROPER,v=i.CONFIGURABLE,b=g.IteratorPrototype,w=g.BUGGY_SAFARI_ITERATORS,E=d("iterator"),x="keys",_="values",S="entries",A=function(){return this};e.exports=function(e,t,n,i,d,g,C){l(n,t,i);var k,O,j,T=function(e){if(e===d&&M)return M;if(!w&&e in P)return P[e];switch(e){case x:case _:case S:return function(){return new n(this,e)}}return function(){return new n(this)}},I=t+" Iterator",N=!1,P=e.prototype,R=P[E]||P["@@iterator"]||d&&P[d],M=!w&&R||T(d),D="Array"==t&&P.entries||R;if(D&&(k=u(D.call(new e)))!==Object.prototype&&k.next&&(a||u(k)===b||(c?c(k,b):s(k[E])||h(k,E,A)),p(k,I,!0,!0),a&&(m[I]=A)),y&&d==_&&R&&R.name!==_&&(!a&&v?f(P,"name",_):(N=!0,M=function(){return o(R,this)})),d)if(O={values:T(_),keys:g?M:T(x),entries:T(S)},C)for(j in O)(w||N||!(j in P))&&h(P,j,O[j]);else r({target:t,proto:!0,forced:w||N},O);return a&&!C||P[E]===M||h(P,E,M,{name:d}),m[t]=M,O}},35143:(e,t,n)=>{"use strict";var r,o,a,i=n(95981),s=n(57475),l=n(10941),u=n(29290),c=n(249),p=n(95929),f=n(99813),h=n(82529),d=f("iterator"),m=!1;[].keys&&("next"in(a=[].keys())?(o=c(c(a)))!==Object.prototype&&(r=o):m=!0),!l(r)||i((function(){var e={};return r[d].call(e)!==e}))?r={}:h&&(r=u(r)),s(r[d])||p(r,d,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:m}},12077:e=>{e.exports={}},10623:(e,t,n)=>{var r=n(43057);e.exports=function(e){return r(e.length)}},35331:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},66132:(e,t,n)=>{var r,o,a,i,s,l=n(21899),u=n(86843),c=n(49677).f,p=n(42941).set,f=n(18397),h=n(22749),d=n(4470),m=n(58045),g=n(6049),y=l.MutationObserver||l.WebKitMutationObserver,v=l.document,b=l.process,w=l.Promise,E=c(l,"queueMicrotask"),x=E&&E.value;if(!x){var _=new f,S=function(){var e,t;for(g&&(e=b.domain)&&e.exit();t=_.get();)try{t()}catch(e){throw _.head&&r(),e}e&&e.enter()};h||g||m||!y||!v?!d&&w&&w.resolve?((i=w.resolve(void 0)).constructor=w,s=u(i.then,i),r=function(){s(S)}):g?r=function(){b.nextTick(S)}:(p=u(p,l),r=function(){p(S)}):(o=!0,a=v.createTextNode(""),new y(S).observe(a,{characterData:!0}),r=function(){a.data=o=!o}),x=function(e){_.head||r(),_.add(e)}}e.exports=x},69520:(e,t,n)=>{"use strict";var r=n(24883),o=TypeError,a=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw o("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new a(e)}},14649:(e,t,n)=>{var r=n(85803);e.exports=function(e,t){return void 0===e?arguments.length<2?"":t:r(e)}},70344:(e,t,n)=>{var r=n(60685),o=TypeError;e.exports=function(e){if(r(e))throw o("The method doesn't accept regular expressions");return e}},24420:(e,t,n)=>{"use strict";var r=n(55746),o=n(95329),a=n(78834),i=n(95981),s=n(14771),l=n(87857),u=n(36760),c=n(89678),p=n(37026),f=Object.assign,h=Object.defineProperty,d=o([].concat);e.exports=!f||i((function(){if(r&&1!==f({b:1},f(h({},"a",{enumerable:!0,get:function(){h(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=f({},e)[n]||s(f({},t)).join("")!=o}))?function(e,t){for(var n=c(e),o=arguments.length,i=1,f=l.f,h=u.f;o>i;)for(var m,g=p(arguments[i++]),y=f?d(s(g),f(g)):s(g),v=y.length,b=0;v>b;)m=y[b++],r&&!a(h,g,m)||(n[m]=g[m]);return n}:f},29290:(e,t,n)=>{var r,o=n(96059),a=n(59938),i=n(56759),s=n(27748),l=n(15463),u=n(61333),c=n(44262),p="prototype",f="script",h=c("IE_PROTO"),d=function(){},m=function(e){return"<"+f+">"+e+""},g=function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t},y=function(){try{r=new ActiveXObject("htmlfile")}catch(e){}var e,t,n;y="undefined"!=typeof document?document.domain&&r?g(r):(t=u("iframe"),n="java"+f+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F):g(r);for(var o=i.length;o--;)delete y[p][i[o]];return y()};s[h]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(d[p]=o(e),n=new d,d[p]=null,n[h]=e):n=y(),void 0===t?n:a.f(n,t)}},59938:(e,t,n)=>{var r=n(55746),o=n(83937),a=n(65988),i=n(96059),s=n(74529),l=n(14771);t.f=r&&!o?Object.defineProperties:function(e,t){i(e);for(var n,r=s(t),o=l(t),u=o.length,c=0;u>c;)a.f(e,n=o[c++],r[n]);return e}},65988:(e,t,n)=>{var r=n(55746),o=n(2840),a=n(83937),i=n(96059),s=n(83894),l=TypeError,u=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p="enumerable",f="configurable",h="writable";t.f=r?a?function(e,t,n){if(i(e),t=s(t),i(n),"function"==typeof e&&"prototype"===t&&"value"in n&&h in n&&!n[h]){var r=c(e,t);r&&r[h]&&(e[t]=n.value,n={configurable:f in n?n[f]:r[f],enumerable:p in n?n[p]:r[p],writable:!1})}return u(e,t,n)}:u:function(e,t,n){if(i(e),t=s(t),i(n),o)try{return u(e,t,n)}catch(e){}if("get"in n||"set"in n)throw l("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},49677:(e,t,n)=>{var r=n(55746),o=n(78834),a=n(36760),i=n(31887),s=n(74529),l=n(83894),u=n(90953),c=n(2840),p=Object.getOwnPropertyDescriptor;t.f=r?p:function(e,t){if(e=s(e),t=l(t),c)try{return p(e,t)}catch(e){}if(u(e,t))return i(!o(a.f,e,t),e[t])}},684:(e,t,n)=>{var r=n(82532),o=n(74529),a=n(10946).f,i=n(15790),s="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return s&&"Window"==r(e)?function(e){try{return a(e)}catch(e){return i(s)}}(e):a(o(e))}},10946:(e,t,n)=>{var r=n(55629),o=n(56759).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},87857:(e,t)=>{t.f=Object.getOwnPropertySymbols},249:(e,t,n)=>{var r=n(90953),o=n(57475),a=n(89678),i=n(44262),s=n(64160),l=i("IE_PROTO"),u=Object,c=u.prototype;e.exports=s?u.getPrototypeOf:function(e){var t=a(e);if(r(t,l))return t[l];var n=t.constructor;return o(n)&&t instanceof n?n.prototype:t instanceof u?c:null}},91584:(e,t,n)=>{var r=n(95981),o=n(10941),a=n(82532),i=n(97135),s=Object.isExtensible,l=r((function(){s(1)}));e.exports=l||i?function(e){return!!o(e)&&((!i||"ArrayBuffer"!=a(e))&&(!s||s(e)))}:s},7046:(e,t,n)=>{var r=n(95329);e.exports=r({}.isPrototypeOf)},55629:(e,t,n)=>{var r=n(95329),o=n(90953),a=n(74529),i=n(31692).indexOf,s=n(27748),l=r([].push);e.exports=function(e,t){var n,r=a(e),u=0,c=[];for(n in r)!o(s,n)&&o(r,n)&&l(c,n);for(;t.length>u;)o(r,n=t[u++])&&(~i(c,n)||l(c,n));return c}},14771:(e,t,n)=>{var r=n(55629),o=n(56759);e.exports=Object.keys||function(e){return r(e,o)}},36760:(e,t)=>{"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,o=r&&!n.call({1:2},1);t.f=o?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},88929:(e,t,n)=>{var r=n(45526),o=n(96059),a=n(11851);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=r(Object.prototype,"__proto__","set"))(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return o(n),a(r),t?e(n,r):n.__proto__=r,n}}():void 0)},88810:(e,t,n)=>{var r=n(55746),o=n(95329),a=n(14771),i=n(74529),s=o(n(36760).f),l=o([].push),u=function(e){return function(t){for(var n,o=i(t),u=a(o),c=u.length,p=0,f=[];c>p;)n=u[p++],r&&!s(o,n)||l(f,e?[n,o[n]]:o[n]);return f}};e.exports={entries:u(!0),values:u(!1)}},95623:(e,t,n)=>{"use strict";var r=n(22885),o=n(9697);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},39811:(e,t,n)=>{var r=n(78834),o=n(57475),a=n(10941),i=TypeError;e.exports=function(e,t){var n,s;if("string"===t&&o(n=e.toString)&&!a(s=r(n,e)))return s;if(o(n=e.valueOf)&&!a(s=r(n,e)))return s;if("string"!==t&&o(n=e.toString)&&!a(s=r(n,e)))return s;throw i("Can't convert object to primitive value")}},31136:(e,t,n)=>{var r=n(626),o=n(95329),a=n(10946),i=n(87857),s=n(96059),l=o([].concat);e.exports=r("Reflect","ownKeys")||function(e){var t=a.f(s(e)),n=i.f;return n?l(t,n(e)):t}},54058:e=>{e.exports={}},40002:e=>{e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},67742:(e,t,n)=>{var r=n(21899),o=n(6991),a=n(57475),i=n(37252),s=n(81302),l=n(99813),u=n(23321),c=n(48501),p=n(82529),f=n(53385),h=o&&o.prototype,d=l("species"),m=!1,g=a(r.PromiseRejectionEvent),y=i("Promise",(function(){var e=s(o),t=e!==String(o);if(!t&&66===f)return!0;if(p&&(!h.catch||!h.finally))return!0;if(!f||f<51||!/native code/.test(e)){var n=new o((function(e){e(1)})),r=function(e){e((function(){}),(function(){}))};if((n.constructor={})[d]=r,!(m=n.then((function(){}))instanceof r))return!0}return!t&&(u||c)&&!g}));e.exports={CONSTRUCTOR:y,REJECTION_EVENT:g,SUBCLASSING:m}},6991:(e,t,n)=>{var r=n(21899);e.exports=r.Promise},56584:(e,t,n)=>{var r=n(96059),o=n(10941),a=n(69520);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=a.f(e);return(0,n.resolve)(t),n.promise}},31542:(e,t,n)=>{var r=n(6991),o=n(21385),a=n(67742).CONSTRUCTOR;e.exports=a||!o((function(e){r.all(e).then(void 0,(function(){}))}))},18397:e=>{var t=function(){this.head=null,this.tail=null};t.prototype={add:function(e){var t={item:e,next:null},n=this.tail;n?n.next=t:this.head=t,this.tail=t},get:function(){var e=this.head;if(e)return null===(this.head=e.next)&&(this.tail=null),e.item}},e.exports=t},48219:(e,t,n)=>{var r=n(82119),o=TypeError;e.exports=function(e){if(r(e))throw o("Can't call method on "+e);return e}},37620:(e,t,n)=>{"use strict";var r,o=n(21899),a=n(79730),i=n(57475),s=n(56491),l=n(2861),u=n(93765),c=n(18348),p=o.Function,f=/MSIE .\./.test(l)||s&&((r=o.Bun.version.split(".")).length<3||0==r[0]&&(r[1]<3||3==r[1]&&0==r[2]));e.exports=function(e,t){var n=t?2:1;return f?function(r,o){var s=c(arguments.length,1)>n,l=i(r)?r:p(r),f=s?u(arguments,n):[],h=s?function(){a(l,this,f)}:l;return t?e(h,o):e(h)}:e}},94431:(e,t,n)=>{"use strict";var r=n(626),o=n(29202),a=n(99813),i=n(55746),s=a("species");e.exports=function(e){var t=r(e);i&&t&&!t[s]&&o(t,s,{configurable:!0,get:function(){return this}})}},90904:(e,t,n)=>{var r=n(22885),o=n(65988).f,a=n(32029),i=n(90953),s=n(95623),l=n(99813)("toStringTag");e.exports=function(e,t,n,u){if(e){var c=n?e:e.prototype;i(c,l)||o(c,l,{configurable:!0,value:t}),u&&!r&&a(c,"toString",s)}}},44262:(e,t,n)=>{var r=n(68726),o=n(99418),a=r("keys");e.exports=function(e){return a[e]||(a[e]=o(e))}},63030:(e,t,n)=>{var r=n(21899),o=n(75609),a="__core-js_shared__",i=r[a]||o(a,{});e.exports=i},68726:(e,t,n)=>{var r=n(82529),o=n(63030);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.28.0",mode:r?"pure":"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.28.0/LICENSE",source:"https://github.com/zloirock/core-js"})},70487:(e,t,n)=>{var r=n(96059),o=n(174),a=n(82119),i=n(99813)("species");e.exports=function(e,t){var n,s=r(e).constructor;return void 0===s||a(n=r(s)[i])?t:o(n)}},64620:(e,t,n)=>{var r=n(95329),o=n(62435),a=n(85803),i=n(48219),s=r("".charAt),l=r("".charCodeAt),u=r("".slice),c=function(e){return function(t,n){var r,c,p=a(i(t)),f=o(n),h=p.length;return f<0||f>=h?e?"":void 0:(r=l(p,f))<55296||r>56319||f+1===h||(c=l(p,f+1))<56320||c>57343?e?s(p,f):r:e?u(p,f,f+2):c-56320+(r-55296<<10)+65536}};e.exports={codeAt:c(!1),charAt:c(!0)}},73291:(e,t,n)=>{var r=n(95329),o=2147483647,a=/[^\0-\u007E]/,i=/[.\u3002\uFF0E\uFF61]/g,s="Overflow: input needs wider integers to process",l=RangeError,u=r(i.exec),c=Math.floor,p=String.fromCharCode,f=r("".charCodeAt),h=r([].join),d=r([].push),m=r("".replace),g=r("".split),y=r("".toLowerCase),v=function(e){return e+22+75*(e<26)},b=function(e,t,n){var r=0;for(e=n?c(e/700):e>>1,e+=c(e/t);e>455;)e=c(e/35),r+=36;return c(r+36*e/(e+38))},w=function(e){var t=[];e=function(e){for(var t=[],n=0,r=e.length;n=55296&&o<=56319&&n=i&&rc((o-u)/E))throw l(s);for(u+=(w-i)*E,i=w,n=0;no)throw l(s);if(r==i){for(var x=u,_=36;;){var S=_<=m?1:_>=m+26?26:_-m;if(x{"use strict";var r=n(62435),o=n(85803),a=n(48219),i=RangeError;e.exports=function(e){var t=o(a(this)),n="",s=r(e);if(s<0||s==1/0)throw i("Wrong number of repetitions");for(;s>0;(s>>>=1)&&(t+=t))1&s&&(n+=t);return n}},93093:(e,t,n)=>{var r=n(79417).PROPER,o=n(95981),a=n(73483);e.exports=function(e){return o((function(){return!!a[e]()||"​…᠎"!=="​…᠎"[e]()||r&&a[e].name!==e}))}},74853:(e,t,n)=>{var r=n(95329),o=n(48219),a=n(85803),i=n(73483),s=r("".replace),l=RegExp("^["+i+"]+"),u=RegExp("(^|[^"+i+"])["+i+"]+$"),c=function(e){return function(t){var n=a(o(t));return 1&e&&(n=s(n,l,"")),2&e&&(n=s(n,u,"$1")),n}};e.exports={start:c(1),end:c(2),trim:c(3)}},63405:(e,t,n)=>{var r=n(53385),o=n(95981);e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},29630:(e,t,n)=>{var r=n(78834),o=n(626),a=n(99813),i=n(95929);e.exports=function(){var e=o("Symbol"),t=e&&e.prototype,n=t&&t.valueOf,s=a("toPrimitive");t&&!t[s]&&i(t,s,(function(e){return r(n,this)}),{arity:1})}},34680:(e,t,n)=>{var r=n(63405);e.exports=r&&!!Symbol.for&&!!Symbol.keyFor},42941:(e,t,n)=>{var r,o,a,i,s=n(21899),l=n(79730),u=n(86843),c=n(57475),p=n(90953),f=n(95981),h=n(15463),d=n(93765),m=n(61333),g=n(18348),y=n(22749),v=n(6049),b=s.setImmediate,w=s.clearImmediate,E=s.process,x=s.Dispatch,_=s.Function,S=s.MessageChannel,A=s.String,C=0,k={},O="onreadystatechange";f((function(){r=s.location}));var j=function(e){if(p(k,e)){var t=k[e];delete k[e],t()}},T=function(e){return function(){j(e)}},I=function(e){j(e.data)},N=function(e){s.postMessage(A(e),r.protocol+"//"+r.host)};b&&w||(b=function(e){g(arguments.length,1);var t=c(e)?e:_(e),n=d(arguments,1);return k[++C]=function(){l(t,void 0,n)},o(C),C},w=function(e){delete k[e]},v?o=function(e){E.nextTick(T(e))}:x&&x.now?o=function(e){x.now(T(e))}:S&&!y?(i=(a=new S).port2,a.port1.onmessage=I,o=u(i.postMessage,i)):s.addEventListener&&c(s.postMessage)&&!s.importScripts&&r&&"file:"!==r.protocol&&!f(N)?(o=N,s.addEventListener("message",I,!1)):o=O in m("script")?function(e){h.appendChild(m("script"))[O]=function(){h.removeChild(this),j(e)}}:function(e){setTimeout(T(e),0)}),e.exports={set:b,clear:w}},59413:(e,t,n)=>{var r=n(62435),o=Math.max,a=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):a(n,t)}},74529:(e,t,n)=>{var r=n(37026),o=n(48219);e.exports=function(e){return r(o(e))}},62435:(e,t,n)=>{var r=n(35331);e.exports=function(e){var t=+e;return t!=t||0===t?0:r(t)}},43057:(e,t,n)=>{var r=n(62435),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},89678:(e,t,n)=>{var r=n(48219),o=Object;e.exports=function(e){return o(r(e))}},46935:(e,t,n)=>{var r=n(78834),o=n(10941),a=n(56664),i=n(14229),s=n(39811),l=n(99813),u=TypeError,c=l("toPrimitive");e.exports=function(e,t){if(!o(e)||a(e))return e;var n,l=i(e,c);if(l){if(void 0===t&&(t="default"),n=r(l,e,t),!o(n)||a(n))return n;throw u("Can't convert object to primitive value")}return void 0===t&&(t="number"),s(e,t)}},83894:(e,t,n)=>{var r=n(46935),o=n(56664);e.exports=function(e){var t=r(e,"string");return o(t)?t:t+""}},22885:(e,t,n)=>{var r={};r[n(99813)("toStringTag")]="z",e.exports="[object z]"===String(r)},85803:(e,t,n)=>{var r=n(9697),o=String;e.exports=function(e){if("Symbol"===r(e))throw TypeError("Cannot convert a Symbol value to a string");return o(e)}},69826:e=>{var t=String;e.exports=function(e){try{return t(e)}catch(e){return"Object"}}},99418:(e,t,n)=>{var r=n(95329),o=0,a=Math.random(),i=r(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+i(++o+a,36)}},14766:(e,t,n)=>{var r=n(95981),o=n(99813),a=n(82529),i=o("iterator");e.exports=!r((function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n="";return e.pathname="c%20d",t.forEach((function(e,r){t.delete("b"),n+=r+e})),a&&!e.toJSON||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[i]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://тест").host||"#%D0%B1"!==new URL("http://a#б").hash||"a1c3"!==n||"x"!==new URL("http://x",void 0).host}))},32302:(e,t,n)=>{var r=n(63405);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(e,t,n)=>{var r=n(55746),o=n(95981);e.exports=r&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},18348:e=>{var t=TypeError;e.exports=function(e,n){if(e{var r=n(21899),o=n(57475),a=r.WeakMap;e.exports=o(a)&&/native code/.test(String(a))},73464:(e,t,n)=>{var r=n(54058),o=n(90953),a=n(11477),i=n(65988).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});o(t,e)||i(t,e,{value:a.f(e)})}},11477:(e,t,n)=>{var r=n(99813);t.f=r},99813:(e,t,n)=>{var r=n(21899),o=n(68726),a=n(90953),i=n(99418),s=n(63405),l=n(32302),u=r.Symbol,c=o("wks"),p=l?u.for||u:u&&u.withoutSetter||i;e.exports=function(e){return a(c,e)||(c[e]=s&&a(u,e)?u[e]:p("Symbol."+e)),c[e]}},73483:e=>{e.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},49812:(e,t,n)=>{"use strict";var r=n(76887),o=n(7046),a=n(249),i=n(88929),s=n(23489),l=n(29290),u=n(32029),c=n(31887),p=n(53794),f=n(79585),h=n(93091),d=n(14649),m=n(99813)("toStringTag"),g=Error,y=[].push,v=function(e,t){var n,r=o(b,this);i?n=i(g(),r?a(this):b):(n=r?this:l(b),u(n,m,"Error")),void 0!==t&&u(n,"message",d(t)),f(n,v,n.stack,1),arguments.length>2&&p(n,arguments[2]);var s=[];return h(e,y,{that:s}),u(n,"errors",s),n};i?i(v,g):s(v,g,{name:!0});var b=v.prototype=l(g.prototype,{constructor:c(1,v),message:c(1,""),name:c(1,"AggregateError")});r({global:!0,constructor:!0,arity:2},{AggregateError:v})},47627:(e,t,n)=>{n(49812)},85906:(e,t,n)=>{"use strict";var r=n(76887),o=n(95981),a=n(1052),i=n(10941),s=n(89678),l=n(10623),u=n(66796),c=n(55449),p=n(64692),f=n(50568),h=n(99813),d=n(53385),m=h("isConcatSpreadable"),g=d>=51||!o((function(){var e=[];return e[m]=!1,e.concat()[0]!==e})),y=function(e){if(!i(e))return!1;var t=e[m];return void 0!==t?!!t:a(e)};r({target:"Array",proto:!0,arity:1,forced:!g||!f("concat")},{concat:function(e){var t,n,r,o,a,i=s(this),f=p(i,0),h=0;for(t=-1,r=arguments.length;t{"use strict";var r=n(76887),o=n(3610).every;r({target:"Array",proto:!0,forced:!n(34194)("every")},{every:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},80290:(e,t,n)=>{var r=n(76887),o=n(91860),a=n(18479);r({target:"Array",proto:!0},{fill:o}),a("fill")},21501:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).filter;r({target:"Array",proto:!0,forced:!n(50568)("filter")},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},44929:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).findIndex,a=n(18479),i="findIndex",s=!0;i in[]&&Array(1)[i]((function(){s=!1})),r({target:"Array",proto:!0,forced:s},{findIndex:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),a(i)},80833:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).find,a=n(18479),i="find",s=!0;i in[]&&Array(1)[i]((function(){s=!1})),r({target:"Array",proto:!0,forced:s},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),a(i)},2437:(e,t,n)=>{"use strict";var r=n(76887),o=n(56837);r({target:"Array",proto:!0,forced:[].forEach!=o},{forEach:o})},53242:(e,t,n)=>{var r=n(76887),o=n(11354);r({target:"Array",stat:!0,forced:!n(21385)((function(e){Array.from(e)}))},{from:o})},97690:(e,t,n)=>{"use strict";var r=n(76887),o=n(31692).includes,a=n(95981),i=n(18479);r({target:"Array",proto:!0,forced:a((function(){return!Array(1).includes()}))},{includes:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("includes")},99076:(e,t,n)=>{"use strict";var r=n(76887),o=n(97484),a=n(31692).indexOf,i=n(34194),s=o([].indexOf),l=!!s&&1/s([1],1,-0)<0;r({target:"Array",proto:!0,forced:l||!i("indexOf")},{indexOf:function(e){var t=arguments.length>1?arguments[1]:void 0;return l?s(this,e,t)||0:a(this,e,t)}})},92737:(e,t,n)=>{n(76887)({target:"Array",stat:!0},{isArray:n(1052)})},66274:(e,t,n)=>{"use strict";var r=n(74529),o=n(18479),a=n(12077),i=n(45402),s=n(65988).f,l=n(75105),u=n(23538),c=n(82529),p=n(55746),f="Array Iterator",h=i.set,d=i.getterFor(f);e.exports=l(Array,"Array",(function(e,t){h(this,{type:f,target:r(e),index:0,kind:t})}),(function(){var e=d(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,u(void 0,!0)):u("keys"==n?r:"values"==n?t[r]:[r,t[r]],!1)}),"values");var m=a.Arguments=a.Array;if(o("keys"),o("values"),o("entries"),!c&&p&&"values"!==m.name)try{s(m,"name",{value:"values"})}catch(e){}},75915:(e,t,n)=>{var r=n(76887),o=n(67145);r({target:"Array",proto:!0,forced:o!==[].lastIndexOf},{lastIndexOf:o})},68787:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).map;r({target:"Array",proto:!0,forced:!n(50568)("map")},{map:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},81876:(e,t,n)=>{"use strict";var r=n(76887),o=n(46499).left,a=n(34194),i=n(53385);r({target:"Array",proto:!0,forced:!n(6049)&&i>79&&i<83||!a("reduce")},{reduce:function(e){var t=arguments.length;return o(this,e,t,t>1?arguments[1]:void 0)}})},60186:(e,t,n)=>{"use strict";var r=n(76887),o=n(1052),a=n(24284),i=n(10941),s=n(59413),l=n(10623),u=n(74529),c=n(55449),p=n(99813),f=n(50568),h=n(93765),d=f("slice"),m=p("species"),g=Array,y=Math.max;r({target:"Array",proto:!0,forced:!d},{slice:function(e,t){var n,r,p,f=u(this),d=l(f),v=s(e,d),b=s(void 0===t?d:t,d);if(o(f)&&(n=f.constructor,(a(n)&&(n===g||o(n.prototype))||i(n)&&null===(n=n[m]))&&(n=void 0),n===g||void 0===n))return h(f,v,b);for(r=new(void 0===n?g:n)(y(b-v,0)),p=0;v{"use strict";var r=n(76887),o=n(3610).some;r({target:"Array",proto:!0,forced:!n(34194)("some")},{some:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},4115:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),a=n(24883),i=n(89678),s=n(10623),l=n(15863),u=n(85803),c=n(95981),p=n(61388),f=n(34194),h=n(34342),d=n(81046),m=n(53385),g=n(18938),y=[],v=o(y.sort),b=o(y.push),w=c((function(){y.sort(void 0)})),E=c((function(){y.sort(null)})),x=f("sort"),_=!c((function(){if(m)return m<70;if(!(h&&h>3)){if(d)return!0;if(g)return g<603;var e,t,n,r,o="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(r=0;r<47;r++)y.push({k:t+r,v:n})}for(y.sort((function(e,t){return t.v-e.v})),r=0;ru(n)?1:-1}}(e)),n=s(o),r=0;r{"use strict";var r=n(76887),o=n(89678),a=n(59413),i=n(62435),s=n(10623),l=n(89779),u=n(66796),c=n(64692),p=n(55449),f=n(15863),h=n(50568)("splice"),d=Math.max,m=Math.min;r({target:"Array",proto:!0,forced:!h},{splice:function(e,t){var n,r,h,g,y,v,b=o(this),w=s(b),E=a(e,w),x=arguments.length;for(0===x?n=r=0:1===x?(n=0,r=w-E):(n=x-2,r=m(d(i(t),0),w-E)),u(w+n-r),h=c(b,r),g=0;gw-r+n;g--)f(b,g-1)}else if(n>r)for(g=w-r;g>E;g--)v=g+n-1,(y=g+r-1)in b?b[v]=b[y]:f(b,v);for(g=0;g{var r=n(76887),o=n(95329),a=Date,i=o(a.prototype.getTime);r({target:"Date",stat:!0},{now:function(){return i(new a)}})},18084:()=>{},73381:(e,t,n)=>{var r=n(76887),o=n(98308);r({target:"Function",proto:!0,forced:Function.bind!==o},{bind:o})},32619:(e,t,n)=>{var r=n(76887),o=n(626),a=n(79730),i=n(78834),s=n(95329),l=n(95981),u=n(57475),c=n(56664),p=n(93765),f=n(33323),h=n(63405),d=String,m=o("JSON","stringify"),g=s(/./.exec),y=s("".charAt),v=s("".charCodeAt),b=s("".replace),w=s(1..toString),E=/[\uD800-\uDFFF]/g,x=/^[\uD800-\uDBFF]$/,_=/^[\uDC00-\uDFFF]$/,S=!h||l((function(){var e=o("Symbol")();return"[null]"!=m([e])||"{}"!=m({a:e})||"{}"!=m(Object(e))})),A=l((function(){return'"\\udf06\\ud834"'!==m("\udf06\ud834")||'"\\udead"'!==m("\udead")})),C=function(e,t){var n=p(arguments),r=f(t);if(u(r)||void 0!==e&&!c(e))return n[1]=function(e,t){if(u(r)&&(t=i(r,this,d(e),t)),!c(t))return t},a(m,null,n)},k=function(e,t,n){var r=y(n,t-1),o=y(n,t+1);return g(x,e)&&!g(_,o)||g(_,e)&&!g(x,r)?"\\u"+w(v(e,0),16):e};m&&r({target:"JSON",stat:!0,arity:3,forced:S||A},{stringify:function(e,t,n){var r=p(arguments),o=a(S?C:m,null,r);return A&&"string"==typeof o?b(o,E,k):o}})},69120:(e,t,n)=>{var r=n(21899);n(90904)(r.JSON,"JSON",!0)},23112:(e,t,n)=>{"use strict";n(24683)("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},37501:(e,t,n)=>{n(23112)},79413:()=>{},49221:(e,t,n)=>{var r=n(76887),o=n(24420);r({target:"Object",stat:!0,arity:2,forced:Object.assign!==o},{assign:o})},74979:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(59938).f;r({target:"Object",stat:!0,forced:Object.defineProperties!==a,sham:!o},{defineProperties:a})},86450:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(65988).f;r({target:"Object",stat:!0,forced:Object.defineProperty!==a,sham:!o},{defineProperty:a})},46924:(e,t,n)=>{var r=n(76887),o=n(95981),a=n(74529),i=n(49677).f,s=n(55746);r({target:"Object",stat:!0,forced:!s||o((function(){i(1)})),sham:!s},{getOwnPropertyDescriptor:function(e,t){return i(a(e),t)}})},88482:(e,t,n)=>{var r=n(76887),o=n(55746),a=n(31136),i=n(74529),s=n(49677),l=n(55449);r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){for(var t,n,r=i(e),o=s.f,u=a(r),c={},p=0;u.length>p;)void 0!==(n=o(r,t=u[p++]))&&l(c,t,n);return c}})},37144:(e,t,n)=>{var r=n(76887),o=n(63405),a=n(95981),i=n(87857),s=n(89678);r({target:"Object",stat:!0,forced:!o||a((function(){i.f(1)}))},{getOwnPropertySymbols:function(e){var t=i.f;return t?t(s(e)):[]}})},21724:(e,t,n)=>{var r=n(76887),o=n(89678),a=n(14771);r({target:"Object",stat:!0,forced:n(95981)((function(){a(1)}))},{keys:function(e){return a(o(e))}})},55967:()=>{},26614:(e,t,n)=>{var r=n(76887),o=n(88810).values;r({target:"Object",stat:!0},{values:function(e){return o(e)}})},4560:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,u=n.reject,c=s((function(){var n=a(t.resolve),i=[],s=0,u=1;l(e,(function(e){var a=s++,l=!1;u++,o(n,t,e).then((function(e){l||(l=!0,i[a]={status:"fulfilled",value:e},--u||r(i))}),(function(e){l||(l=!0,i[a]={status:"rejected",reason:e},--u||r(i))}))})),--u||r(i)}));return c.error&&u(c.value),n.promise}})},16890:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{all:function(e){var t=this,n=i.f(t),r=n.resolve,u=n.reject,c=s((function(){var n=a(t.resolve),i=[],s=0,c=1;l(e,(function(e){var a=s++,l=!1;c++,o(n,t,e).then((function(e){l||(l=!0,i[a]=e,--c||r(i))}),u)})),--c||r(i)}));return c.error&&u(c.value),n.promise}})},91302:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(626),s=n(69520),l=n(40002),u=n(93091),c=n(31542),p="No one promise resolved";r({target:"Promise",stat:!0,forced:c},{any:function(e){var t=this,n=i("AggregateError"),r=s.f(t),c=r.resolve,f=r.reject,h=l((function(){var r=a(t.resolve),i=[],s=0,l=1,h=!1;u(e,(function(e){var a=s++,u=!1;l++,o(r,t,e).then((function(e){u||h||(h=!0,c(e))}),(function(e){u||h||(u=!0,i[a]=e,--l||f(new n(i,p)))}))})),--l||f(new n(i,p))}));return h.error&&f(h.value),r.promise}})},83376:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),a=n(67742).CONSTRUCTOR,i=n(6991),s=n(626),l=n(57475),u=n(95929),c=i&&i.prototype;if(r({target:"Promise",proto:!0,forced:a,real:!0},{catch:function(e){return this.then(void 0,e)}}),!o&&l(i)){var p=s("Promise").prototype.catch;c.catch!==p&&u(c,"catch",p,{unsafe:!0})}},26934:(e,t,n)=>{"use strict";var r,o,a,i=n(76887),s=n(82529),l=n(6049),u=n(21899),c=n(78834),p=n(95929),f=n(88929),h=n(90904),d=n(94431),m=n(24883),g=n(57475),y=n(10941),v=n(5743),b=n(70487),w=n(42941).set,E=n(66132),x=n(34845),_=n(40002),S=n(18397),A=n(45402),C=n(6991),k=n(67742),O=n(69520),j="Promise",T=k.CONSTRUCTOR,I=k.REJECTION_EVENT,N=k.SUBCLASSING,P=A.getterFor(j),R=A.set,M=C&&C.prototype,D=C,L=M,B=u.TypeError,F=u.document,U=u.process,z=O.f,q=z,$=!!(F&&F.createEvent&&u.dispatchEvent),V="unhandledrejection",W=function(e){var t;return!(!y(e)||!g(t=e.then))&&t},H=function(e,t){var n,r,o,a=t.value,i=1==t.state,s=i?e.ok:e.fail,l=e.resolve,u=e.reject,p=e.domain;try{s?(i||(2===t.rejection&&Y(t),t.rejection=1),!0===s?n=a:(p&&p.enter(),n=s(a),p&&(p.exit(),o=!0)),n===e.promise?u(B("Promise-chain cycle")):(r=W(n))?c(r,n,l,u):l(n)):u(a)}catch(e){p&&!o&&p.exit(),u(e)}},J=function(e,t){e.notified||(e.notified=!0,E((function(){for(var n,r=e.reactions;n=r.get();)H(n,e);e.notified=!1,t&&!e.rejection&&G(e)})))},K=function(e,t,n){var r,o;$?((r=F.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),u.dispatchEvent(r)):r={promise:t,reason:n},!I&&(o=u["on"+e])?o(r):e===V&&x("Unhandled promise rejection",n)},G=function(e){c(w,u,(function(){var t,n=e.facade,r=e.value;if(Z(e)&&(t=_((function(){l?U.emit("unhandledRejection",r,n):K(V,n,r)})),e.rejection=l||Z(e)?2:1,t.error))throw t.value}))},Z=function(e){return 1!==e.rejection&&!e.parent},Y=function(e){c(w,u,(function(){var t=e.facade;l?U.emit("rejectionHandled",t):K("rejectionhandled",t,e.value)}))},Q=function(e,t,n){return function(r){e(t,r,n)}},X=function(e,t,n){e.done||(e.done=!0,n&&(e=n),e.value=t,e.state=2,J(e,!0))},ee=function(e,t,n){if(!e.done){e.done=!0,n&&(e=n);try{if(e.facade===t)throw B("Promise can't be resolved itself");var r=W(t);r?E((function(){var n={done:!1};try{c(r,t,Q(ee,n,e),Q(X,n,e))}catch(t){X(n,t,e)}})):(e.value=t,e.state=1,J(e,!1))}catch(t){X({done:!1},t,e)}}};if(T&&(L=(D=function(e){v(this,L),m(e),c(r,this);var t=P(this);try{e(Q(ee,t),Q(X,t))}catch(e){X(t,e)}}).prototype,(r=function(e){R(this,{type:j,done:!1,notified:!1,parent:!1,reactions:new S,rejection:!1,state:0,value:void 0})}).prototype=p(L,"then",(function(e,t){var n=P(this),r=z(b(this,D));return n.parent=!0,r.ok=!g(e)||e,r.fail=g(t)&&t,r.domain=l?U.domain:void 0,0==n.state?n.reactions.add(r):E((function(){H(r,n)})),r.promise})),o=function(){var e=new r,t=P(e);this.promise=e,this.resolve=Q(ee,t),this.reject=Q(X,t)},O.f=z=function(e){return e===D||undefined===e?new o(e):q(e)},!s&&g(C)&&M!==Object.prototype)){a=M.then,N||p(M,"then",(function(e,t){var n=this;return new D((function(e,t){c(a,n,e,t)})).then(e,t)}),{unsafe:!0});try{delete M.constructor}catch(e){}f&&f(M,L)}i({global:!0,constructor:!0,wrap:!0,forced:T},{Promise:D}),h(D,j,!1,!0),d(j)},44349:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),a=n(6991),i=n(95981),s=n(626),l=n(57475),u=n(70487),c=n(56584),p=n(95929),f=a&&a.prototype;if(r({target:"Promise",proto:!0,real:!0,forced:!!a&&i((function(){f.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=u(this,s("Promise")),n=l(e);return this.then(n?function(n){return c(t,e()).then((function(){return n}))}:e,n?function(n){return c(t,e()).then((function(){throw n}))}:e)}}),!o&&l(a)){var h=s("Promise").prototype.finally;f.finally!==h&&p(f,"finally",h,{unsafe:!0})}},98881:(e,t,n)=>{n(26934),n(16890),n(83376),n(55921),n(64069),n(14482)},55921:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(24883),i=n(69520),s=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{race:function(e){var t=this,n=i.f(t),r=n.reject,u=s((function(){var i=a(t.resolve);l(e,(function(e){o(i,t,e).then(n.resolve,r)}))}));return u.error&&r(u.value),n.promise}})},64069:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),a=n(69520);r({target:"Promise",stat:!0,forced:n(67742).CONSTRUCTOR},{reject:function(e){var t=a.f(this);return o(t.reject,void 0,e),t.promise}})},14482:(e,t,n)=>{"use strict";var r=n(76887),o=n(626),a=n(82529),i=n(6991),s=n(67742).CONSTRUCTOR,l=n(56584),u=o("Promise"),c=a&&!s;r({target:"Promise",stat:!0,forced:a||s},{resolve:function(e){return l(c&&this===u?i:this,e)}})},1502:()=>{},11035:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),a=n(70344),i=n(48219),s=n(85803),l=n(67772),u=o("".indexOf);r({target:"String",proto:!0,forced:!l("includes")},{includes:function(e){return!!~u(s(i(this)),s(a(e)),arguments.length>1?arguments[1]:void 0)}})},77971:(e,t,n)=>{"use strict";var r=n(64620).charAt,o=n(85803),a=n(45402),i=n(75105),s=n(23538),l="String Iterator",u=a.set,c=a.getterFor(l);i(String,"String",(function(e){u(this,{type:l,string:o(e),index:0})}),(function(){var e,t=c(this),n=t.string,o=t.index;return o>=n.length?s(void 0,!0):(e=r(n,o),t.index+=e.length,s(e,!1))}))},60986:(e,t,n)=>{n(76887)({target:"String",proto:!0},{repeat:n(16178)})},94761:(e,t,n)=>{"use strict";var r,o=n(76887),a=n(97484),i=n(49677).f,s=n(43057),l=n(85803),u=n(70344),c=n(48219),p=n(67772),f=n(82529),h=a("".startsWith),d=a("".slice),m=Math.min,g=p("startsWith");o({target:"String",proto:!0,forced:!!(f||g||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!g},{startsWith:function(e){var t=l(c(this));u(e);var n=s(m(arguments.length>1?arguments[1]:void 0,t.length)),r=l(e);return h?h(t,r,n):d(t,n,n+r.length)===r}})},57398:(e,t,n)=>{"use strict";var r=n(76887),o=n(74853).trim;r({target:"String",proto:!0,forced:n(93093)("trim")},{trim:function(){return o(this)}})},8555:(e,t,n)=>{n(73464)("asyncIterator")},48616:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),a=n(78834),i=n(95329),s=n(82529),l=n(55746),u=n(63405),c=n(95981),p=n(90953),f=n(7046),h=n(96059),d=n(74529),m=n(83894),g=n(85803),y=n(31887),v=n(29290),b=n(14771),w=n(10946),E=n(684),x=n(87857),_=n(49677),S=n(65988),A=n(59938),C=n(36760),k=n(95929),O=n(29202),j=n(68726),T=n(44262),I=n(27748),N=n(99418),P=n(99813),R=n(11477),M=n(73464),D=n(29630),L=n(90904),B=n(45402),F=n(3610).forEach,U=T("hidden"),z="Symbol",q="prototype",$=B.set,V=B.getterFor(z),W=Object[q],H=o.Symbol,J=H&&H[q],K=o.TypeError,G=o.QObject,Z=_.f,Y=S.f,Q=E.f,X=C.f,ee=i([].push),te=j("symbols"),ne=j("op-symbols"),re=j("wks"),oe=!G||!G[q]||!G[q].findChild,ae=l&&c((function(){return 7!=v(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=Z(W,t);r&&delete W[t],Y(e,t,n),r&&e!==W&&Y(W,t,r)}:Y,ie=function(e,t){var n=te[e]=v(J);return $(n,{type:z,tag:e,description:t}),l||(n.description=t),n},se=function(e,t,n){e===W&&se(ne,t,n),h(e);var r=m(t);return h(n),p(te,r)?(n.enumerable?(p(e,U)&&e[U][r]&&(e[U][r]=!1),n=v(n,{enumerable:y(0,!1)})):(p(e,U)||Y(e,U,y(1,{})),e[U][r]=!0),ae(e,r,n)):Y(e,r,n)},le=function(e,t){h(e);var n=d(t),r=b(n).concat(fe(n));return F(r,(function(t){l&&!a(ue,n,t)||se(e,t,n[t])})),e},ue=function(e){var t=m(e),n=a(X,this,t);return!(this===W&&p(te,t)&&!p(ne,t))&&(!(n||!p(this,t)||!p(te,t)||p(this,U)&&this[U][t])||n)},ce=function(e,t){var n=d(e),r=m(t);if(n!==W||!p(te,r)||p(ne,r)){var o=Z(n,r);return!o||!p(te,r)||p(n,U)&&n[U][r]||(o.enumerable=!0),o}},pe=function(e){var t=Q(d(e)),n=[];return F(t,(function(e){p(te,e)||p(I,e)||ee(n,e)})),n},fe=function(e){var t=e===W,n=Q(t?ne:d(e)),r=[];return F(n,(function(e){!p(te,e)||t&&!p(W,e)||ee(r,te[e])})),r};u||(k(J=(H=function(){if(f(J,this))throw K("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?g(arguments[0]):void 0,t=N(e),n=function(e){this===W&&a(n,ne,e),p(this,U)&&p(this[U],t)&&(this[U][t]=!1),ae(this,t,y(1,e))};return l&&oe&&ae(W,t,{configurable:!0,set:n}),ie(t,e)})[q],"toString",(function(){return V(this).tag})),k(H,"withoutSetter",(function(e){return ie(N(e),e)})),C.f=ue,S.f=se,A.f=le,_.f=ce,w.f=E.f=pe,x.f=fe,R.f=function(e){return ie(P(e),e)},l&&(O(J,"description",{configurable:!0,get:function(){return V(this).description}}),s||k(W,"propertyIsEnumerable",ue,{unsafe:!0}))),r({global:!0,constructor:!0,wrap:!0,forced:!u,sham:!u},{Symbol:H}),F(b(re),(function(e){M(e)})),r({target:z,stat:!0,forced:!u},{useSetter:function(){oe=!0},useSimple:function(){oe=!1}}),r({target:"Object",stat:!0,forced:!u,sham:!l},{create:function(e,t){return void 0===t?v(e):le(v(e),t)},defineProperty:se,defineProperties:le,getOwnPropertyDescriptor:ce}),r({target:"Object",stat:!0,forced:!u},{getOwnPropertyNames:pe}),D(),L(H,z),I[U]=!0},52615:()=>{},64523:(e,t,n)=>{var r=n(76887),o=n(626),a=n(90953),i=n(85803),s=n(68726),l=n(34680),u=s("string-to-symbol-registry"),c=s("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=i(e);if(a(u,t))return u[t];var n=o("Symbol")(t);return u[t]=n,c[n]=t,n}})},21732:(e,t,n)=>{n(73464)("hasInstance")},35903:(e,t,n)=>{n(73464)("isConcatSpreadable")},1825:(e,t,n)=>{n(73464)("iterator")},35824:(e,t,n)=>{n(48616),n(64523),n(38608),n(32619),n(37144)},38608:(e,t,n)=>{var r=n(76887),o=n(90953),a=n(56664),i=n(69826),s=n(68726),l=n(34680),u=s("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{keyFor:function(e){if(!a(e))throw TypeError(i(e)+" is not a symbol");if(o(u,e))return u[e]}})},45915:(e,t,n)=>{n(73464)("matchAll")},28394:(e,t,n)=>{n(73464)("match")},61766:(e,t,n)=>{n(73464)("replace")},62737:(e,t,n)=>{n(73464)("search")},89911:(e,t,n)=>{n(73464)("species")},74315:(e,t,n)=>{n(73464)("split")},63131:(e,t,n)=>{var r=n(73464),o=n(29630);r("toPrimitive"),o()},64714:(e,t,n)=>{var r=n(626),o=n(73464),a=n(90904);o("toStringTag"),a(r("Symbol"),"Symbol")},70659:(e,t,n)=>{n(73464)("unscopables")},28783:(e,t,n)=>{n(73464)("asyncDispose")},43975:(e,t,n)=>{n(73464)("dispose")},22731:(e,t,n)=>{var r=n(76887),o=n(626),a=n(95329),i=o("Symbol"),s=i.keyFor,l=a(i.prototype.valueOf);r({target:"Symbol",stat:!0},{isRegistered:function(e){try{return void 0!==s(l(e))}catch(e){return!1}}})},85605:(e,t,n)=>{for(var r=n(76887),o=n(68726),a=n(626),i=n(95329),s=n(56664),l=n(99813),u=a("Symbol"),c=u.isWellKnown,p=a("Object","getOwnPropertyNames"),f=i(u.prototype.valueOf),h=o("wks"),d=0,m=p(u),g=m.length;d{n(73464)("matcher")},31943:(e,t,n)=>{n(73464)("metadataKey")},45414:(e,t,n)=>{n(73464)("metadata")},46774:(e,t,n)=>{n(73464)("observable")},80620:(e,t,n)=>{n(73464)("patternMatch")},36172:(e,t,n)=>{n(73464)("replaceAll")},7634:(e,t,n)=>{n(66274);var r=n(63281),o=n(21899),a=n(9697),i=n(32029),s=n(12077),l=n(99813)("toStringTag");for(var u in r){var c=o[u],p=c&&c.prototype;p&&a(p)!==l&&i(p,l,u),s[u]=s.Array}},79229:(e,t,n)=>{var r=n(76887),o=n(21899),a=n(37620)(o.setInterval,!0);r({global:!0,bind:!0,forced:o.setInterval!==a},{setInterval:a})},17749:(e,t,n)=>{var r=n(76887),o=n(21899),a=n(37620)(o.setTimeout,!0);r({global:!0,bind:!0,forced:o.setTimeout!==a},{setTimeout:a})},71249:(e,t,n)=>{n(79229),n(17749)},62524:(e,t,n)=>{"use strict";n(66274);var r=n(76887),o=n(21899),a=n(78834),i=n(95329),s=n(55746),l=n(14766),u=n(95929),c=n(94380),p=n(90904),f=n(53847),h=n(45402),d=n(5743),m=n(57475),g=n(90953),y=n(86843),v=n(9697),b=n(96059),w=n(10941),E=n(85803),x=n(29290),_=n(31887),S=n(53476),A=n(22902),C=n(18348),k=n(99813),O=n(61388),j=k("iterator"),T="URLSearchParams",I=T+"Iterator",N=h.set,P=h.getterFor(T),R=h.getterFor(I),M=Object.getOwnPropertyDescriptor,D=function(e){if(!s)return o[e];var t=M(o,e);return t&&t.value},L=D("fetch"),B=D("Request"),F=D("Headers"),U=B&&B.prototype,z=F&&F.prototype,q=o.RegExp,$=o.TypeError,V=o.decodeURIComponent,W=o.encodeURIComponent,H=i("".charAt),J=i([].join),K=i([].push),G=i("".replace),Z=i([].shift),Y=i([].splice),Q=i("".split),X=i("".slice),ee=/\+/g,te=Array(4),ne=function(e){return te[e-1]||(te[e-1]=q("((?:%[\\da-f]{2}){"+e+"})","gi"))},re=function(e){try{return V(e)}catch(t){return e}},oe=function(e){var t=G(e,ee," "),n=4;try{return V(t)}catch(e){for(;n;)t=G(t,ne(n--),re);return t}},ae=/[!'()~]|%20/g,ie={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},se=function(e){return ie[e]},le=function(e){return G(W(e),ae,se)},ue=f((function(e,t){N(this,{type:I,iterator:S(P(e).entries),kind:t})}),"Iterator",(function(){var e=R(this),t=e.kind,n=e.iterator.next(),r=n.value;return n.done||(n.value="keys"===t?r.key:"values"===t?r.value:[r.key,r.value]),n}),!0),ce=function(e){this.entries=[],this.url=null,void 0!==e&&(w(e)?this.parseObject(e):this.parseQuery("string"==typeof e?"?"===H(e,0)?X(e,1):e:E(e)))};ce.prototype={type:T,bindURL:function(e){this.url=e,this.update()},parseObject:function(e){var t,n,r,o,i,s,l,u=A(e);if(u)for(n=(t=S(e,u)).next;!(r=a(n,t)).done;){if(i=(o=S(b(r.value))).next,(s=a(i,o)).done||(l=a(i,o)).done||!a(i,o).done)throw $("Expected sequence with length 2");K(this.entries,{key:E(s.value),value:E(l.value)})}else for(var c in e)g(e,c)&&K(this.entries,{key:c,value:E(e[c])})},parseQuery:function(e){if(e)for(var t,n,r=Q(e,"&"),o=0;o0?arguments[0]:void 0))},fe=pe.prototype;if(c(fe,{append:function(e,t){C(arguments.length,2);var n=P(this);K(n.entries,{key:E(e),value:E(t)}),n.updateURL()},delete:function(e){C(arguments.length,1);for(var t=P(this),n=t.entries,r=E(e),o=0;ot.key?1:-1})),e.updateURL()},forEach:function(e){for(var t,n=P(this).entries,r=y(e,arguments.length>1?arguments[1]:void 0),o=0;o1?me(arguments[1]):{})}}),m(B)){var ge=function(e){return d(this,U),new B(e,arguments.length>1?me(arguments[1]):{})};U.constructor=ge,ge.prototype=U,r({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:ge})}}e.exports={URLSearchParams:pe,getState:P}},95304:(e,t,n)=>{n(62524)},47250:(e,t,n)=>{"use strict";n(77971);var r,o=n(76887),a=n(55746),i=n(14766),s=n(21899),l=n(86843),u=n(95329),c=n(95929),p=n(29202),f=n(5743),h=n(90953),d=n(24420),m=n(11354),g=n(15790),y=n(64620).codeAt,v=n(73291),b=n(85803),w=n(90904),E=n(18348),x=n(62524),_=n(45402),S=_.set,A=_.getterFor("URL"),C=x.URLSearchParams,k=x.getState,O=s.URL,j=s.TypeError,T=s.parseInt,I=Math.floor,N=Math.pow,P=u("".charAt),R=u(/./.exec),M=u([].join),D=u(1..toString),L=u([].pop),B=u([].push),F=u("".replace),U=u([].shift),z=u("".split),q=u("".slice),$=u("".toLowerCase),V=u([].unshift),W="Invalid scheme",H="Invalid host",J="Invalid port",K=/[a-z]/i,G=/[\d+-.a-z]/i,Z=/\d/,Y=/^0x/i,Q=/^[0-7]+$/,X=/^\d+$/,ee=/^[\da-f]+$/i,te=/[\0\t\n\r #%/:<>?@[\\\]^|]/,ne=/[\0\t\n\r #/:<>?@[\\\]^|]/,re=/^[\u0000-\u0020]+/,oe=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,ae=/[\t\n\r]/g,ie=function(e){var t,n,r,o;if("number"==typeof e){for(t=[],n=0;n<4;n++)V(t,e%256),e=I(e/256);return M(t,".")}if("object"==typeof e){for(t="",r=function(e){for(var t=null,n=1,r=null,o=0,a=0;a<8;a++)0!==e[a]?(o>n&&(t=r,n=o),r=null,o=0):(null===r&&(r=a),++o);return o>n&&(t=r,n=o),t}(e),n=0;n<8;n++)o&&0===e[n]||(o&&(o=!1),r===n?(t+=n?":":"::",o=!0):(t+=D(e[n],16),n<7&&(t+=":")));return"["+t+"]"}return e},se={},le=d({},se,{" ":1,'"':1,"<":1,">":1,"`":1}),ue=d({},le,{"#":1,"?":1,"{":1,"}":1}),ce=d({},ue,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),pe=function(e,t){var n=y(e,0);return n>32&&n<127&&!h(t,e)?e:encodeURIComponent(e)},fe={ftp:21,file:null,http:80,https:443,ws:80,wss:443},he=function(e,t){var n;return 2==e.length&&R(K,P(e,0))&&(":"==(n=P(e,1))||!t&&"|"==n)},de=function(e){var t;return e.length>1&&he(q(e,0,2))&&(2==e.length||"/"===(t=P(e,2))||"\\"===t||"?"===t||"#"===t)},me=function(e){return"."===e||"%2e"===$(e)},ge={},ye={},ve={},be={},we={},Ee={},xe={},_e={},Se={},Ae={},Ce={},ke={},Oe={},je={},Te={},Ie={},Ne={},Pe={},Re={},Me={},De={},Le=function(e,t,n){var r,o,a,i=b(e);if(t){if(o=this.parse(i))throw j(o);this.searchParams=null}else{if(void 0!==n&&(r=new Le(n,!0)),o=this.parse(i,null,r))throw j(o);(a=k(new C)).bindURL(this),this.searchParams=a}};Le.prototype={type:"URL",parse:function(e,t,n){var o,a,i,s,l,u=this,c=t||ge,p=0,f="",d=!1,y=!1,v=!1;for(e=b(e),t||(u.scheme="",u.username="",u.password="",u.host=null,u.port=null,u.path=[],u.query=null,u.fragment=null,u.cannotBeABaseURL=!1,e=F(e,re,""),e=F(e,oe,"$1")),e=F(e,ae,""),o=m(e);p<=o.length;){switch(a=o[p],c){case ge:if(!a||!R(K,a)){if(t)return W;c=ve;continue}f+=$(a),c=ye;break;case ye:if(a&&(R(G,a)||"+"==a||"-"==a||"."==a))f+=$(a);else{if(":"!=a){if(t)return W;f="",c=ve,p=0;continue}if(t&&(u.isSpecial()!=h(fe,f)||"file"==f&&(u.includesCredentials()||null!==u.port)||"file"==u.scheme&&!u.host))return;if(u.scheme=f,t)return void(u.isSpecial()&&fe[u.scheme]==u.port&&(u.port=null));f="","file"==u.scheme?c=je:u.isSpecial()&&n&&n.scheme==u.scheme?c=be:u.isSpecial()?c=_e:"/"==o[p+1]?(c=we,p++):(u.cannotBeABaseURL=!0,B(u.path,""),c=Re)}break;case ve:if(!n||n.cannotBeABaseURL&&"#"!=a)return W;if(n.cannotBeABaseURL&&"#"==a){u.scheme=n.scheme,u.path=g(n.path),u.query=n.query,u.fragment="",u.cannotBeABaseURL=!0,c=De;break}c="file"==n.scheme?je:Ee;continue;case be:if("/"!=a||"/"!=o[p+1]){c=Ee;continue}c=Se,p++;break;case we:if("/"==a){c=Ae;break}c=Pe;continue;case Ee:if(u.scheme=n.scheme,a==r)u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query=n.query;else if("/"==a||"\\"==a&&u.isSpecial())c=xe;else if("?"==a)u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query="",c=Me;else{if("#"!=a){u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.path.length--,c=Pe;continue}u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,u.path=g(n.path),u.query=n.query,u.fragment="",c=De}break;case xe:if(!u.isSpecial()||"/"!=a&&"\\"!=a){if("/"!=a){u.username=n.username,u.password=n.password,u.host=n.host,u.port=n.port,c=Pe;continue}c=Ae}else c=Se;break;case _e:if(c=Se,"/"!=a||"/"!=P(f,p+1))continue;p++;break;case Se:if("/"!=a&&"\\"!=a){c=Ae;continue}break;case Ae:if("@"==a){d&&(f="%40"+f),d=!0,i=m(f);for(var w=0;w65535)return J;u.port=u.isSpecial()&&_===fe[u.scheme]?null:_,f=""}if(t)return;c=Ne;continue}return J}f+=a;break;case je:if(u.scheme="file","/"==a||"\\"==a)c=Te;else{if(!n||"file"!=n.scheme){c=Pe;continue}if(a==r)u.host=n.host,u.path=g(n.path),u.query=n.query;else if("?"==a)u.host=n.host,u.path=g(n.path),u.query="",c=Me;else{if("#"!=a){de(M(g(o,p),""))||(u.host=n.host,u.path=g(n.path),u.shortenPath()),c=Pe;continue}u.host=n.host,u.path=g(n.path),u.query=n.query,u.fragment="",c=De}}break;case Te:if("/"==a||"\\"==a){c=Ie;break}n&&"file"==n.scheme&&!de(M(g(o,p),""))&&(he(n.path[0],!0)?B(u.path,n.path[0]):u.host=n.host),c=Pe;continue;case Ie:if(a==r||"/"==a||"\\"==a||"?"==a||"#"==a){if(!t&&he(f))c=Pe;else if(""==f){if(u.host="",t)return;c=Ne}else{if(s=u.parseHost(f))return s;if("localhost"==u.host&&(u.host=""),t)return;f="",c=Ne}continue}f+=a;break;case Ne:if(u.isSpecial()){if(c=Pe,"/"!=a&&"\\"!=a)continue}else if(t||"?"!=a)if(t||"#"!=a){if(a!=r&&(c=Pe,"/"!=a))continue}else u.fragment="",c=De;else u.query="",c=Me;break;case Pe:if(a==r||"/"==a||"\\"==a&&u.isSpecial()||!t&&("?"==a||"#"==a)){if(".."===(l=$(l=f))||"%2e."===l||".%2e"===l||"%2e%2e"===l?(u.shortenPath(),"/"==a||"\\"==a&&u.isSpecial()||B(u.path,"")):me(f)?"/"==a||"\\"==a&&u.isSpecial()||B(u.path,""):("file"==u.scheme&&!u.path.length&&he(f)&&(u.host&&(u.host=""),f=P(f,0)+":"),B(u.path,f)),f="","file"==u.scheme&&(a==r||"?"==a||"#"==a))for(;u.path.length>1&&""===u.path[0];)U(u.path);"?"==a?(u.query="",c=Me):"#"==a&&(u.fragment="",c=De)}else f+=pe(a,ue);break;case Re:"?"==a?(u.query="",c=Me):"#"==a?(u.fragment="",c=De):a!=r&&(u.path[0]+=pe(a,se));break;case Me:t||"#"!=a?a!=r&&("'"==a&&u.isSpecial()?u.query+="%27":u.query+="#"==a?"%23":pe(a,se)):(u.fragment="",c=De);break;case De:a!=r&&(u.fragment+=pe(a,le))}p++}},parseHost:function(e){var t,n,r;if("["==P(e,0)){if("]"!=P(e,e.length-1))return H;if(t=function(e){var t,n,r,o,a,i,s,l=[0,0,0,0,0,0,0,0],u=0,c=null,p=0,f=function(){return P(e,p)};if(":"==f()){if(":"!=P(e,1))return;p+=2,c=++u}for(;f();){if(8==u)return;if(":"!=f()){for(t=n=0;n<4&&R(ee,f());)t=16*t+T(f(),16),p++,n++;if("."==f()){if(0==n)return;if(p-=n,u>6)return;for(r=0;f();){if(o=null,r>0){if(!("."==f()&&r<4))return;p++}if(!R(Z,f()))return;for(;R(Z,f());){if(a=T(f(),10),null===o)o=a;else{if(0==o)return;o=10*o+a}if(o>255)return;p++}l[u]=256*l[u]+o,2!=++r&&4!=r||u++}if(4!=r)return;break}if(":"==f()){if(p++,!f())return}else if(f())return;l[u++]=t}else{if(null!==c)return;p++,c=++u}}if(null!==c)for(i=u-c,u=7;0!=u&&i>0;)s=l[u],l[u--]=l[c+i-1],l[c+--i]=s;else if(8!=u)return;return l}(q(e,1,-1)),!t)return H;this.host=t}else if(this.isSpecial()){if(e=v(e),R(te,e))return H;if(t=function(e){var t,n,r,o,a,i,s,l=z(e,".");if(l.length&&""==l[l.length-1]&&l.length--,(t=l.length)>4)return e;for(n=[],r=0;r1&&"0"==P(o,0)&&(a=R(Y,o)?16:8,o=q(o,8==a?1:2)),""===o)i=0;else{if(!R(10==a?X:8==a?Q:ee,o))return e;i=T(o,a)}B(n,i)}for(r=0;r=N(256,5-t))return null}else if(i>255)return null;for(s=L(n),r=0;r1?arguments[1]:void 0,r=S(t,new Le(e,!1,n));a||(t.href=r.serialize(),t.origin=r.getOrigin(),t.protocol=r.getProtocol(),t.username=r.getUsername(),t.password=r.getPassword(),t.host=r.getHost(),t.hostname=r.getHostname(),t.port=r.getPort(),t.pathname=r.getPathname(),t.search=r.getSearch(),t.searchParams=r.getSearchParams(),t.hash=r.getHash())},Fe=Be.prototype,Ue=function(e,t){return{get:function(){return A(this)[e]()},set:t&&function(e){return A(this)[t](e)},configurable:!0,enumerable:!0}};if(a&&(p(Fe,"href",Ue("serialize","setHref")),p(Fe,"origin",Ue("getOrigin")),p(Fe,"protocol",Ue("getProtocol","setProtocol")),p(Fe,"username",Ue("getUsername","setUsername")),p(Fe,"password",Ue("getPassword","setPassword")),p(Fe,"host",Ue("getHost","setHost")),p(Fe,"hostname",Ue("getHostname","setHostname")),p(Fe,"port",Ue("getPort","setPort")),p(Fe,"pathname",Ue("getPathname","setPathname")),p(Fe,"search",Ue("getSearch","setSearch")),p(Fe,"searchParams",Ue("getSearchParams")),p(Fe,"hash",Ue("getHash","setHash"))),c(Fe,"toJSON",(function(){return A(this).serialize()}),{enumerable:!0}),c(Fe,"toString",(function(){return A(this).serialize()}),{enumerable:!0}),O){var ze=O.createObjectURL,qe=O.revokeObjectURL;ze&&c(Be,"createObjectURL",l(ze,O)),qe&&c(Be,"revokeObjectURL",l(qe,O))}w(Be,"URL"),o({global:!0,constructor:!0,forced:!i,sham:!a},{URL:Be})},33601:(e,t,n)=>{n(47250)},98947:()=>{},24848:(e,t,n)=>{var r=n(54493);e.exports=r},83363:(e,t,n)=>{var r=n(24034);e.exports=r},62908:(e,t,n)=>{var r=n(12710);e.exports=r},49216:(e,t,n)=>{var r=n(99324);e.exports=r},56668:(e,t,n)=>{var r=n(95909);e.exports=r},74719:(e,t,n)=>{var r=n(14423);e.exports=r},57784:(e,t,n)=>{var r=n(81103);e.exports=r},28196:(e,t,n)=>{var r=n(16246);e.exports=r},8065:(e,t,n)=>{var r=n(56043);e.exports=r},57448:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(62908),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.entries;return e===s||a(s,e)&&t===s.entries||o(l,r(e))?i:t}},29455:(e,t,n)=>{var r=n(13160);e.exports=r},69743:(e,t,n)=>{var r=n(80446);e.exports=r},11955:(e,t,n)=>{var r=n(2480);e.exports=r},96064:(e,t,n)=>{var r=n(7147);e.exports=r},61577:(e,t,n)=>{var r=n(32236);e.exports=r},46279:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(49216),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.forEach;return e===s||a(s,e)&&t===s.forEach||o(l,r(e))?i:t}},33778:(e,t,n)=>{var r=n(58557);e.exports=r},19373:(e,t,n)=>{var r=n(34570);e.exports=r},73819:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(56668),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.keys;return e===s||a(s,e)&&t===s.keys||o(l,r(e))?i:t}},11022:(e,t,n)=>{var r=n(57564);e.exports=r},61798:(e,t,n)=>{var r=n(88287);e.exports=r},52527:(e,t,n)=>{var r=n(68025);e.exports=r},36857:(e,t,n)=>{var r=n(59257);e.exports=r},82073:(e,t,n)=>{var r=n(69601);e.exports=r},45286:(e,t,n)=>{var r=n(28299);e.exports=r},62856:(e,t,n)=>{var r=n(69355);e.exports=r},2348:(e,t,n)=>{var r=n(18339);e.exports=r},35178:(e,t,n)=>{var r=n(71611);e.exports=r},76361:(e,t,n)=>{var r=n(62774);e.exports=r},71815:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),a=n(7046),i=n(74719),s=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.values;return e===s||a(s,e)&&t===s.values||o(l,r(e))?i:t}},8933:(e,t,n)=>{var r=n(84426);e.exports=r},15868:(e,t,n)=>{var r=n(91018);n(7634),e.exports=r},63383:(e,t,n)=>{var r=n(45999);e.exports=r},57396:(e,t,n)=>{var r=n(7702);e.exports=r},41910:(e,t,n)=>{var r=n(48171);e.exports=r},79427:(e,t,n)=>{var r=n(286);e.exports=r},62857:(e,t,n)=>{var r=n(92766);e.exports=r},9534:(e,t,n)=>{var r=n(30498);e.exports=r},23059:(e,t,n)=>{var r=n(48494);e.exports=r},47795:(e,t,n)=>{var r=n(98430);e.exports=r},27460:(e,t,n)=>{var r=n(52956);n(7634),e.exports=r},27989:(e,t,n)=>{n(71249);var r=n(54058);e.exports=r.setTimeout},92547:(e,t,n)=>{var r=n(57473);n(7634),e.exports=r},46509:(e,t,n)=>{var r=n(24227);n(7634),e.exports=r},35774:(e,t,n)=>{var r=n(62978);e.exports=r},57641:(e,t,n)=>{var r=n(71459);e.exports=r},71459:(e,t,n)=>{n(33601),n(98947),n(95304);var r=n(54058);e.exports=r.URL},31905:function(){!function(e){!function(t){var n={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(n.arrayBuffer)var r=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],o=ArrayBuffer.isView||function(e){return e&&r.indexOf(Object.prototype.toString.call(e))>-1};function a(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function i(e){return"string"!=typeof e&&(e=String(e)),e}function s(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return n.iterable&&(t[Symbol.iterator]=function(){return t}),t}function l(e){this.map={},e instanceof l?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function u(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function c(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function p(e){var t=new FileReader,n=c(t);return t.readAsArrayBuffer(e),n}function f(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function h(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:n.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:n.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():n.arrayBuffer&&n.blob&&((t=e)&&DataView.prototype.isPrototypeOf(t))?(this._bodyArrayBuffer=f(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):n.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||o(e))?this._bodyArrayBuffer=f(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},n.blob&&(this.blob=function(){var e=u(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?u(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(p)}),this.text=function(){var e,t,n,r=u(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,n=c(t),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function g(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function y(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new l(t.headers),this.url=t.url||"",this._initBody(e)}m.prototype.clone=function(){return new m(this,{body:this._bodyInit})},h.call(m.prototype),h.call(y.prototype),y.prototype.clone=function(){return new y(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new l(this.headers),url:this.url})},y.error=function(){var e=new y(null,{status:0,statusText:""});return e.type="error",e};var v=[301,302,303,307,308];y.redirect=function(e,t){if(-1===v.indexOf(t))throw new RangeError("Invalid status code");return new y(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function b(e,r){return new Promise((function(o,a){var i=new m(e,r);if(i.signal&&i.signal.aborted)return a(new t.DOMException("Aborted","AbortError"));var s=new XMLHttpRequest;function u(){s.abort()}s.onload=function(){var e,t,n={status:s.status,statusText:s.statusText,headers:(e=s.getAllResponseHeaders()||"",t=new l,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in s?s.responseURL:n.headers.get("X-Request-URL");var r="response"in s?s.response:s.responseText;o(new y(r,n))},s.onerror=function(){a(new TypeError("Network request failed"))},s.ontimeout=function(){a(new TypeError("Network request failed"))},s.onabort=function(){a(new t.DOMException("Aborted","AbortError"))},s.open(i.method,i.url,!0),"include"===i.credentials?s.withCredentials=!0:"omit"===i.credentials&&(s.withCredentials=!1),"responseType"in s&&n.blob&&(s.responseType="blob"),i.headers.forEach((function(e,t){s.setRequestHeader(t,e)})),i.signal&&(i.signal.addEventListener("abort",u),s.onreadystatechange=function(){4===s.readyState&&i.signal.removeEventListener("abort",u)}),s.send(void 0===i._bodyInit?null:i._bodyInit)}))}b.polyfill=!0,e.fetch||(e.fetch=b,e.Headers=l,e.Request=m,e.Response=y),t.Headers=l,t.Request=m,t.Response=y,t.fetch=b,Object.defineProperty(t,"__esModule",{value:!0})}({})}("undefined"!=typeof self?self:this)},8269:function(e,t,n){var r;r=void 0!==n.g?n.g:this,e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,n=String(e),r=n.length,o=-1,a="",i=n.charCodeAt(0);++o=1&&t<=31||127==t||0==o&&t>=48&&t<=57||1==o&&t>=48&&t<=57&&45==i?"\\"+t.toString(16)+" ":0==o&&1==r&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+n.charAt(o):n.charAt(o):a+="�";return a};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(r)},27698:(e,t,n)=>{"use strict";var r=n(48764).Buffer;function o(e){return e instanceof r||e instanceof Date||e instanceof RegExp}function a(e){if(e instanceof r){var t=r.alloc?r.alloc(e.length):new r(e.length);return e.copy(t),t}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function i(e){var t=[];return e.forEach((function(e,n){"object"==typeof e&&null!==e?Array.isArray(e)?t[n]=i(e):o(e)?t[n]=a(e):t[n]=l({},e):t[n]=e})),t}function s(e,t){return"__proto__"===t?void 0:e[t]}var l=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,n=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(r){"object"!=typeof r||null===r||Array.isArray(r)||Object.keys(r).forEach((function(u){return t=s(n,u),(e=s(r,u))===n?void 0:"object"!=typeof e||null===e?void(n[u]=e):Array.isArray(e)?void(n[u]=i(e)):o(e)?void(n[u]=a(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(n[u]=l({},e)):void(n[u]=l(t,e))}))})),n}},9996:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===n}(e)}(e)};var n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?l((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function o(e,t,n){return e.concat(t).map((function(e){return r(e,n)}))}function a(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function i(e,t){try{return t in e}catch(e){return!1}}function s(e,t,n){var o={};return n.isMergeableObject(e)&&a(e).forEach((function(t){o[t]=r(e[t],n)})),a(t).forEach((function(a){(function(e,t){return i(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,a)||(i(e,a)&&n.isMergeableObject(t[a])?o[a]=function(e,t){if(!t.customMerge)return l;var n=t.customMerge(e);return"function"==typeof n?n:l}(a,n)(e[a],t[a],n):o[a]=r(t[a],n))})),o}function l(e,n,a){(a=a||{}).arrayMerge=a.arrayMerge||o,a.isMergeableObject=a.isMergeableObject||t,a.cloneUnlessOtherwiseSpecified=r;var i=Array.isArray(n);return i===Array.isArray(e)?i?a.arrayMerge(e,n,a):s(e,n,a):r(n,a)}l.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return l(e,n,t)}),{})};var u=l;e.exports=u},27856:function(e){e.exports=function(){"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,n){return t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},t(e,n)}function n(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function r(e,o,a){return r=n()?Reflect.construct:function(e,n,r){var o=[null];o.push.apply(o,n);var a=new(Function.bind.apply(e,o));return r&&t(a,r.prototype),a},r.apply(null,arguments)}function o(e){return a(e)||i(e)||s(e)||u()}function a(e){if(Array.isArray(e))return l(e)}function i(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function s(e,t){if(e){if("string"==typeof e)return l(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?l(e,t):void 0}}function l(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?n-1:0),o=1;o/gm),G=g(/^data-[\-\w.\u00B7-\uFFFF]/),Z=g(/^aria-[\-\w]+$/),Y=g(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Q=g(/^(?:\w+script|data):/i),X=g(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ee=g(/^html$/i),te=function(){return"undefined"==typeof window?null:window},ne=function(t,n){if("object"!==e(t)||"function"!=typeof t.createPolicy)return null;var r=null,o="data-tt-policy-suffix";n.currentScript&&n.currentScript.hasAttribute(o)&&(r=n.currentScript.getAttribute(o));var a="dompurify"+(r?"#"+r:"");try{return t.createPolicy(a,{createHTML:function(e){return e},createScriptURL:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};function re(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:te(),n=function(e){return re(e)};if(n.version="2.3.10",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,a=t.document,i=t.DocumentFragment,s=t.HTMLTemplateElement,l=t.Node,u=t.Element,c=t.NodeFilter,p=t.NamedNodeMap,f=void 0===p?t.NamedNodeMap||t.MozNamedAttrMap:p,h=t.HTMLFormElement,d=t.DOMParser,g=t.trustedTypes,y=u.prototype,v=M(y,"cloneNode"),b=M(y,"nextSibling"),w=M(y,"childNodes"),I=M(y,"parentNode");if("function"==typeof s){var N=a.createElement("template");N.content&&N.content.ownerDocument&&(a=N.content.ownerDocument)}var oe=ne(g,r),ae=oe?oe.createHTML(""):"",ie=a,se=ie.implementation,le=ie.createNodeIterator,ue=ie.createDocumentFragment,ce=ie.getElementsByTagName,pe=r.importNode,fe={};try{fe=R(a).documentMode?a.documentMode:{}}catch(e){}var he={};n.isSupported="function"==typeof I&&se&&void 0!==se.createHTMLDocument&&9!==fe;var de,me,ge=J,ye=K,ve=G,be=Z,we=Q,Ee=X,xe=Y,_e=null,Se=P({},[].concat(o(D),o(L),o(B),o(U),o(q))),Ae=null,Ce=P({},[].concat(o($),o(V),o(W),o(H))),ke=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Oe=null,je=null,Te=!0,Ie=!0,Ne=!1,Pe=!1,Re=!1,Me=!1,De=!1,Le=!1,Be=!1,Fe=!1,Ue=!0,ze=!0,qe=!1,$e={},Ve=null,We=P({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),He=null,Je=P({},["audio","video","img","source","image","track"]),Ke=null,Ge=P({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ze="http://www.w3.org/1998/Math/MathML",Ye="http://www.w3.org/2000/svg",Qe="http://www.w3.org/1999/xhtml",Xe=Qe,et=!1,tt=["application/xhtml+xml","text/html"],nt="text/html",rt=null,ot=a.createElement("form"),at=function(e){return e instanceof RegExp||e instanceof Function},it=function(t){rt&&rt===t||(t&&"object"===e(t)||(t={}),t=R(t),de=de=-1===tt.indexOf(t.PARSER_MEDIA_TYPE)?nt:t.PARSER_MEDIA_TYPE,me="application/xhtml+xml"===de?function(e){return e}:S,_e="ALLOWED_TAGS"in t?P({},t.ALLOWED_TAGS,me):Se,Ae="ALLOWED_ATTR"in t?P({},t.ALLOWED_ATTR,me):Ce,Ke="ADD_URI_SAFE_ATTR"in t?P(R(Ge),t.ADD_URI_SAFE_ATTR,me):Ge,He="ADD_DATA_URI_TAGS"in t?P(R(Je),t.ADD_DATA_URI_TAGS,me):Je,Ve="FORBID_CONTENTS"in t?P({},t.FORBID_CONTENTS,me):We,Oe="FORBID_TAGS"in t?P({},t.FORBID_TAGS,me):{},je="FORBID_ATTR"in t?P({},t.FORBID_ATTR,me):{},$e="USE_PROFILES"in t&&t.USE_PROFILES,Te=!1!==t.ALLOW_ARIA_ATTR,Ie=!1!==t.ALLOW_DATA_ATTR,Ne=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Pe=t.SAFE_FOR_TEMPLATES||!1,Re=t.WHOLE_DOCUMENT||!1,Le=t.RETURN_DOM||!1,Be=t.RETURN_DOM_FRAGMENT||!1,Fe=t.RETURN_TRUSTED_TYPE||!1,De=t.FORCE_BODY||!1,Ue=!1!==t.SANITIZE_DOM,ze=!1!==t.KEEP_CONTENT,qe=t.IN_PLACE||!1,xe=t.ALLOWED_URI_REGEXP||xe,Xe=t.NAMESPACE||Qe,t.CUSTOM_ELEMENT_HANDLING&&at(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(ke.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&at(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(ke.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(ke.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Pe&&(Ie=!1),Be&&(Le=!0),$e&&(_e=P({},o(q)),Ae=[],!0===$e.html&&(P(_e,D),P(Ae,$)),!0===$e.svg&&(P(_e,L),P(Ae,V),P(Ae,H)),!0===$e.svgFilters&&(P(_e,B),P(Ae,V),P(Ae,H)),!0===$e.mathMl&&(P(_e,U),P(Ae,W),P(Ae,H))),t.ADD_TAGS&&(_e===Se&&(_e=R(_e)),P(_e,t.ADD_TAGS,me)),t.ADD_ATTR&&(Ae===Ce&&(Ae=R(Ae)),P(Ae,t.ADD_ATTR,me)),t.ADD_URI_SAFE_ATTR&&P(Ke,t.ADD_URI_SAFE_ATTR,me),t.FORBID_CONTENTS&&(Ve===We&&(Ve=R(Ve)),P(Ve,t.FORBID_CONTENTS,me)),ze&&(_e["#text"]=!0),Re&&P(_e,["html","head","body"]),_e.table&&(P(_e,["tbody"]),delete Oe.tbody),m&&m(t),rt=t)},st=P({},["mi","mo","mn","ms","mtext"]),lt=P({},["foreignobject","desc","title","annotation-xml"]),ut=P({},["title","style","font","a","script"]),ct=P({},L);P(ct,B),P(ct,F);var pt=P({},U);P(pt,z);var ft=function(e){var t=I(e);t&&t.tagName||(t={namespaceURI:Qe,tagName:"template"});var n=S(e.tagName),r=S(t.tagName);return e.namespaceURI===Ye?t.namespaceURI===Qe?"svg"===n:t.namespaceURI===Ze?"svg"===n&&("annotation-xml"===r||st[r]):Boolean(ct[n]):e.namespaceURI===Ze?t.namespaceURI===Qe?"math"===n:t.namespaceURI===Ye?"math"===n&<[r]:Boolean(pt[n]):e.namespaceURI===Qe&&!(t.namespaceURI===Ye&&!lt[r])&&!(t.namespaceURI===Ze&&!st[r])&&!pt[n]&&(ut[n]||!ct[n])},ht=function(e){_(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ae}catch(t){e.remove()}}},dt=function(e,t){try{_(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){_(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Ae[e])if(Le||Be)try{ht(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},mt=function(e){var t,n;if(De)e=""+e;else{var r=A(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===de&&(e=''+e+"");var o=oe?oe.createHTML(e):e;if(Xe===Qe)try{t=(new d).parseFromString(o,de)}catch(e){}if(!t||!t.documentElement){t=se.createDocument(Xe,"template",null);try{t.documentElement.innerHTML=et?"":o}catch(e){}}var i=t.body||t.documentElement;return e&&n&&i.insertBefore(a.createTextNode(n),i.childNodes[0]||null),Xe===Qe?ce.call(t,Re?"html":"body")[0]:Re?t.documentElement:i},gt=function(e){return le.call(e.ownerDocument||e,e,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT,null,!1)},yt=function(e){return e instanceof h&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof f)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)},vt=function(t){return"object"===e(l)?t instanceof l:t&&"object"===e(t)&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},bt=function(e,t,r){he[e]&&E(he[e],(function(e){e.call(n,t,r,rt)}))},wt=function(e){var t;if(bt("beforeSanitizeElements",e,null),yt(e))return ht(e),!0;if(j(/[\u0080-\uFFFF]/,e.nodeName))return ht(e),!0;var r=me(e.nodeName);if(bt("uponSanitizeElement",e,{tagName:r,allowedTags:_e}),e.hasChildNodes()&&!vt(e.firstElementChild)&&(!vt(e.content)||!vt(e.content.firstElementChild))&&j(/<[/\w]/g,e.innerHTML)&&j(/<[/\w]/g,e.textContent))return ht(e),!0;if("select"===r&&j(/