初次提交代码
This commit is contained in:
23
.env.development
Normal file
23
.env.development
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 环境配置
|
||||||
|
ENVIRONMENT=local # 可选值: local/development/test/production
|
||||||
|
# 钉钉相关配置
|
||||||
|
# DINGDING_INVOICE_APPROVAL_PROCESS_CODE = PROC-4A680AF4-4853-4C30-A660-E702442E184A
|
||||||
|
# DINGDING_APP_KEY = dingjrkdm6lqfebrwrhs
|
||||||
|
# DINGDING_APP_SECRET = oxYwPO9F6-_TbjI-uKFpO1__Qc3jRPQ5MSInBvei2r6rscg_gZexbAqi-QR6ZNCz
|
||||||
|
DINGDING_INVOICE_APPROVAL_PROCESS_CODE = PROC-4FB03ECE-6F80-413F-90C3-CC9DCA903A2F
|
||||||
|
DINGDING_APP_KEY = ding3fdr9ulnhzlvnznb
|
||||||
|
DINGDING_APP_SECRET = Jk9Ph6iQ0gmkSxjOTZ7Sx3q_Z9HfJ97SbHoB2M6FFTNBrXqXAg-h1npfyAtznB7i
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_URL=mysql://root:root@localhost:3306/procurement_db?serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
|
||||||
|
DATABASE_NAME=your_database_name
|
||||||
|
DATABASE_USER=your_database_username
|
||||||
|
DATABASE_PASSWORD=your_database_password
|
||||||
|
|
||||||
|
PRISMA_ENGINES_MIRROR=https://registry.npmmirror.com/-/binary/prisma
|
||||||
|
|
||||||
|
# 下载文件的基础目录配置
|
||||||
|
# Windows示例: DOWNLOAD_BASE_DIR=E:\\nginx-1.29.1\\html\\download
|
||||||
|
# Linux示例: DOWNLOAD_BASE_DIR=/var/www/html/download
|
||||||
|
DOWNLOAD_BASE_DIR=D:\\nginx-1.29.1\\html\\download
|
||||||
|
DOWNLOAD_BASE_URL=http://localhost/download
|
||||||
4
.env.example
Normal file
4
.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 下载文件的基础目录配置
|
||||||
|
# Windows示例: DOWNLOAD_BASE_DIR=E:\\nginx-1.29.1\\html\\download
|
||||||
|
# Linux示例: DOWNLOAD_BASE_DIR=/var/www/html/download
|
||||||
|
DOWNLOAD_BASE_DIR=./filebase
|
||||||
21
.env.production
Normal file
21
.env.production
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 环境配置
|
||||||
|
ENVIRONMENT=production # 可选值: development/test/production
|
||||||
|
# 钉钉相关配置
|
||||||
|
DINGDING_INVOICE_APPROVAL_PROCESS_CODE = PROC-1FBC7990-CB26-45BA-8EE3-472EA70E4421
|
||||||
|
DINGDING_APP_KEY = ding3fdr9ulnhzlvnznb
|
||||||
|
DINGDING_APP_SECRET = Jk9Ph6iQ0gmkSxjOTZ7Sx3q_Z9HfJ97SbHoB2M6FFTNBrXqXAg-h1npfyAtznB7i
|
||||||
|
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DATABASE_URL=mysql://mysqladmin:Hgt9908265!@@10.124.9.120:3306/procurement_db?serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
|
||||||
|
DATABASE_NAME=procurement_db
|
||||||
|
DATABASE_USER=mysqladmin # 后续考虑增加一个用户
|
||||||
|
DATABASE_PASSWORD=Hgt9908265!@
|
||||||
|
|
||||||
|
PRISMA_ENGINES_MIRROR=https://registry.npmmirror.com/-/binary/prisma
|
||||||
|
|
||||||
|
# 下载文件的基础目录配置
|
||||||
|
# Windows示例: DOWNLOAD_BASE_DIR=E:\\nginx-1.29.1\\html\\download
|
||||||
|
# Linux示例: DOWNLOAD_BASE_DIR=/var/www/html/download
|
||||||
|
DOWNLOAD_BASE_DIR=/usr/share/nginx/html/download
|
||||||
|
DOWNLOAD_BASE_URL=http://hrsscip.dfmc.com.cn/download
|
||||||
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
# .env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
combined.log
|
||||||
|
|
||||||
|
filebase
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
9
.idea/dingding-oa-server.iml
generated
Normal file
9
.idea/dingding-oa-server.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/dingding-oa-server.iml" filepath="$PROJECT_DIR$/.idea/dingding-oa-server.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Next.js: 调试服务端",
|
||||||
|
"type": "node-terminal",
|
||||||
|
"request": "launch",
|
||||||
|
"command": "pnpm run dev:dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: 调试客户端",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: 调试客户端 (Firefox)",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"reAttach": true,
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"url": "webpack://_N_E",
|
||||||
|
"path": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Next.js: 全栈调试",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
|
||||||
|
"runtimeArgs": ["--inspect"],
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"serverReadyAction": {
|
||||||
|
"action": "debugWithEdge",
|
||||||
|
"killOnServerStop": true,
|
||||||
|
"pattern": "- Local:.+(https?://.+)",
|
||||||
|
"uriFormat": "%s",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
93
README.md
93
README.md
@@ -1,2 +1,93 @@
|
|||||||
# dingding-oa-server
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
# 定时脚本
|
||||||
|
sudo crontab -l
|
||||||
|
0 */6 * * * curl http://localhost:3000/api/dingding/corn >> /var/log/cron_dingding.log 2>&1
|
||||||
|
每六个小时执行一次脚本将数据注入到数据库
|
||||||
|
|
||||||
|
# 通用Excel导入接口
|
||||||
|
|
||||||
|
项目提供了一个通用的Excel数据导入接口,可以用于向不同的数据表导入数据。
|
||||||
|
|
||||||
|
## 接口地址
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/utils/genericImport?table=<tableName>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的表名
|
||||||
|
|
||||||
|
- project (项目立项数据)
|
||||||
|
- purchase_contract_order_management (采购合同订单管理数据)
|
||||||
|
- purchase_contract_execution_maintenance (采购合同履约数据)
|
||||||
|
- expense_reimbursement (费用报销数据)
|
||||||
|
- actual_payment (实际支付数据)
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
发送POST请求到上述接口地址,将Excel文件作为请求体上传,同时在查询参数中指定要导入的表名。
|
||||||
|
|
||||||
|
示例(使用curl):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 导入项目立项数据
|
||||||
|
curl -X POST -F "file=@project_data.xlsx" "http://localhost:3000/api/utils/genericImport?table=project"
|
||||||
|
|
||||||
|
# 导入采购合同订单管理数据
|
||||||
|
curl -X POST -F "file=@purchase_contract_order.xlsx" "http://localhost:3000/api/utils/genericImport?table=purchase_contract_order_management"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excel文件格式要求
|
||||||
|
|
||||||
|
Excel文件应包含与目标数据表对应的列,第一行为列标题。其中必须包含"数据id"列作为唯一标识。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 接口在插入数据前会查询数据库中已存在的记录,自动过滤掉重复的数据(基于数据id字段),只插入新数据
|
||||||
|
2. 导入完成后会返回具体插入的新数据条数
|
||||||
|
3. 导入过程中如遇到错误,会返回500状态码及错误信息
|
||||||
|
4. 请确保Excel文件格式正确,列名与模板一致
|
||||||
|
|
||||||
|
|
||||||
|
## 金融数据计算库
|
||||||
|
https://mikemcl.github.io/bignumber.js/
|
||||||
|
https://www.npmjs.com/package/bignumber.js
|
||||||
|
用例 1:浮点数精度误差(最常见问题)
|
||||||
|
用例 2:大整数精度丢失
|
||||||
|
用例 3:toFixed()方法的舍入异常
|
||||||
16
eslint.config.mjs
Normal file
16
eslint.config.mjs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
16
next.config.ts
Normal file
16
next.config.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
typescript: {
|
||||||
|
ignoreBuildErrors: true
|
||||||
|
},
|
||||||
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true
|
||||||
|
},
|
||||||
|
/* config options here */
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认解析器
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
51
package.json
Normal file
51
package.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "dingding-oa-server",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev:prod": "cross-env NODE_ENV=production && next dev --turbopack",
|
||||||
|
"dev:dev-db": "cross-env NODE_ENV=development && dotenv -e .env.development -- npx prisma db pull && npx prisma generate && next dev --turbopack ",
|
||||||
|
"dev:dev": "cross-env NODE_ENV=development && next dev --turbopack ",
|
||||||
|
"build": " cross-env NODE_ENV=production && dotenv -e .env.production -- npx prisma db pull && npx prisma generate && next build",
|
||||||
|
"start": " cross-env NODE_ENV=production && next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"pulldb": "dotenv -e .env.development -- npx prisma db pull && npx prisma generate"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alicloud/dingtalk": "^2.2.21",
|
||||||
|
"@alicloud/openapi-client": "^0.4.14",
|
||||||
|
"@alicloud/tea-util": "^1.4.10",
|
||||||
|
"prisma": "6.19.0",
|
||||||
|
"@prisma/client": "6.19.0",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
|
"bignumber.js": "^9.3.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"dotenv-cli": "^10.0.0",
|
||||||
|
"formidable": "^3.5.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"next": "15.3.4",
|
||||||
|
"next-multiparty": "^0.7.0",
|
||||||
|
"p-limit": "^7.1.1",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"sax": "^1.4.1",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"xlsx-style": "^0.8.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/archiver": "^6.0.3",
|
||||||
|
"@types/formidable": "^3.4.5",
|
||||||
|
"@types/lodash": "^4.17.20",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.3.4",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
5052
pnpm-lock.yaml
generated
Normal file
5052
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
postcss.config.mjs
Normal file
5
postcss.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
4106
procurement_db.sql
Normal file
4106
procurement_db.sql
Normal file
File diff suppressed because it is too large
Load Diff
1
public/file.svg
Normal file
1
public/file.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
1
public/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
1
public/next.svg
Normal file
1
public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
1
public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
1
public/window.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
426
schema.prisma
Normal file
426
schema.prisma
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["views"]
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "mysql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model dingding_oa_invoice_application {
|
||||||
|
invoiceJobNumber Int @default(autoincrement())
|
||||||
|
dataId String @db.VarChar(255)
|
||||||
|
applicationDate String? @db.VarChar(255)
|
||||||
|
applicant String? @db.VarChar(255)
|
||||||
|
applicationDepartment String? @db.VarChar(255)
|
||||||
|
agreementType String? @db.VarChar(255)
|
||||||
|
xiang_mu_zu_bie String? @db.VarChar(255)
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan String? @db.VarChar(255)
|
||||||
|
businessStartTime String? @db.VarChar(255)
|
||||||
|
businessEndTime String? @db.VarChar(255)
|
||||||
|
contractNumber String? @db.VarChar(255)
|
||||||
|
contractName String? @db.VarChar(255)
|
||||||
|
uploadedContractAttachment String? @db.Text
|
||||||
|
uploadedSealedCostListDa String? @db.Text
|
||||||
|
uploadedSealedCostListDu String? @db.Text
|
||||||
|
projectNumber String? @db.VarChar(255)
|
||||||
|
is_tax_inclusive String? @db.VarChar(255)
|
||||||
|
invoice_tax_rate String? @db.VarChar(255)
|
||||||
|
invoice_tax_category String? @db.VarChar(255)
|
||||||
|
is_multi_project_merge String? @db.VarChar(255)
|
||||||
|
customerCompanyName String? @db.VarChar(255)
|
||||||
|
customerCompanyNameOther String? @db.VarChar(255)
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma String? @db.VarChar(255)
|
||||||
|
bankName String? @db.VarChar(255)
|
||||||
|
bankAccount String? @db.VarChar(255)
|
||||||
|
kai_piao_shi_you String? @db.VarChar(255)
|
||||||
|
fa_piao_bei_zhu_lan_tian_xie_nei_rong String? @db.Text
|
||||||
|
invoiceType String? @db.VarChar(50)
|
||||||
|
invoiceCode Int? @db.UnsignedTinyInt
|
||||||
|
business_content_du String? @db.VarChar(255)
|
||||||
|
invoice_amount_yuan_du String? @db.VarChar(255)
|
||||||
|
invoice_amount_yuan_other String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
user String? @db.VarChar(255)
|
||||||
|
remark String? @db.VarChar(255)
|
||||||
|
cancelReason String? @db.VarChar(255)
|
||||||
|
canceldataid String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
han_shui_zong_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
han_shui_zong_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_du_da_xie String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_other_da_xie String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_other_da_xie String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_duo_da_xie String? @db.VarChar(255)
|
||||||
|
approvalNumber String? @db.VarChar(255)
|
||||||
|
creationTime String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
currentResponsiblePerson String? @db.VarChar(255)
|
||||||
|
approvalResult String? @db.VarChar(255)
|
||||||
|
approvalStatus String? @db.VarChar(255)
|
||||||
|
updateTime String? @db.VarChar(255)
|
||||||
|
creatorDepartment String? @db.VarChar(255)
|
||||||
|
approvalFormTitle String? @db.VarChar(255)
|
||||||
|
historicalApprovers String? @db.VarChar(255)
|
||||||
|
timeConsumed String? @db.VarChar(255)
|
||||||
|
approvalRecord String? @db.VarChar(255)
|
||||||
|
payee String? @db.VarChar(255)
|
||||||
|
invoice_amount_yuan String? @db.VarChar(255)
|
||||||
|
invoice_amount_yuan_da String? @db.VarChar(255)
|
||||||
|
BigInvoiceAmountList String? @db.Text
|
||||||
|
multiProjectMergeInvoice String? @db.Text
|
||||||
|
is_single_contract_multi_customer String? @db.VarChar(255)
|
||||||
|
single_contract_multi_customer_invoice_list String? @db.Text
|
||||||
|
hong_chong_piao_hao String? @db.VarChar(50)
|
||||||
|
|
||||||
|
@@id([invoiceJobNumber, dataId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model invoice_amount_details {
|
||||||
|
id Int @default(autoincrement())
|
||||||
|
dataid String @db.VarChar(255)
|
||||||
|
agreementType String? @db.VarChar(50)
|
||||||
|
project_number String? @db.VarChar(255)
|
||||||
|
business_group_name String? @db.VarChar(100)
|
||||||
|
project_name String? @db.VarChar(255)
|
||||||
|
customer_company_name String? @db.VarChar(255)
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma String? @db.VarChar(255)
|
||||||
|
bank_name String? @db.VarChar(255)
|
||||||
|
bank_account String? @db.VarChar(255)
|
||||||
|
business_content String? @db.VarChar(255)
|
||||||
|
invoice_amount String? @db.VarChar(255)
|
||||||
|
payee String? @db.VarChar(255)
|
||||||
|
updatedAt DateTime @default(now()) @db.DateTime(0)
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben String? @db.VarChar(255)
|
||||||
|
|
||||||
|
@@id([id, dataid])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model invoice_data {
|
||||||
|
dataid String @db.VarChar(255)
|
||||||
|
piao_hao String @id @db.VarChar(50)
|
||||||
|
kai_piao_ri_qi String @db.VarChar(50)
|
||||||
|
hui_kuan_ri_qi String @db.VarChar(255)
|
||||||
|
hui_kuan_jin_e String @db.VarChar(255)
|
||||||
|
created_at DateTime @default(now()) @db.DateTime(0)
|
||||||
|
updated_at DateTime @default(now()) @db.DateTime(0)
|
||||||
|
invoice_type_enum Int? @default(0)
|
||||||
|
zhang_qi String? @db.VarChar(255)
|
||||||
|
customer_company_name String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
model test {
|
||||||
|
id Int @id
|
||||||
|
name String? @db.VarChar(255)
|
||||||
|
shcool String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model project {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
project_department String? @db.VarChar(255)
|
||||||
|
is_multi_client String? @db.VarChar(255)
|
||||||
|
multi_client_name String? @db.VarChar(255)
|
||||||
|
group_abbreviation String? @db.VarChar(255)
|
||||||
|
previous_project_id String? @db.VarChar(255)
|
||||||
|
project_id String? @db.VarChar(255)
|
||||||
|
project_name String? @db.VarChar(255)
|
||||||
|
contract_id String? @db.VarChar(255)
|
||||||
|
contract_name String? @db.VarChar(255)
|
||||||
|
client_name String? @db.VarChar(255)
|
||||||
|
unit_nature String? @db.VarChar(255)
|
||||||
|
project_leader String? @db.VarChar(255)
|
||||||
|
create_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_title String? @db.VarChar(255)
|
||||||
|
update_time String? @db.VarChar(255)
|
||||||
|
project_group String? @db.VarChar(255)
|
||||||
|
delivery_area String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model purchase_contract_order_management {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
purchase_contract_number String? @db.VarChar(255)
|
||||||
|
purchase_purpose String? @db.VarChar(255)
|
||||||
|
project_number String? @db.VarChar(255)
|
||||||
|
project_name String? @db.VarChar(255)
|
||||||
|
associated_sales_contract_number String? @db.VarChar(255)
|
||||||
|
contract_name String? @db.VarChar(255)
|
||||||
|
purchase_approval_number String? @db.VarChar(255)
|
||||||
|
purchase_contract_name String? @db.VarChar(255)
|
||||||
|
xiang_dui_fang String? @db.VarChar(255)
|
||||||
|
he_tong_lei_bie String? @db.VarChar(255)
|
||||||
|
he_tong_zong_jin_e_HanShui String? @db.VarChar(255)
|
||||||
|
sheng_yu_jin_e_HanShui String? @db.VarChar(255)
|
||||||
|
he_tong_sheng_yu_jin_e_ti_xing String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_ming_cheng String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_jin_e_HanShui String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_lu_yue_kai_shi_shi_jian String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_lu_yue_jie_shu_shi_jian String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_fa_qi_bu_men String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_fa_qi_ren String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_fa_qi_shi_jian String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_guan_lian_he_tong_fujian String? @db.VarChar(255)
|
||||||
|
ben_ci_ding_dan_wen_ben String? @db.Text
|
||||||
|
approval_number String? @db.VarChar(255)
|
||||||
|
creation_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
current_responsible_person String? @db.VarChar(255)
|
||||||
|
approval_result String? @db.VarChar(255)
|
||||||
|
approval_status String? @db.VarChar(255)
|
||||||
|
update_time String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_form_title String? @db.VarChar(255)
|
||||||
|
time_consumed String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model purchase_contract_execution_maintenance {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
initiator String? @db.VarChar(255)
|
||||||
|
contract_number String? @db.VarChar(255)
|
||||||
|
contract_name String? @db.VarChar(255)
|
||||||
|
lvyue_faqi_bumen String? @db.VarChar(255)
|
||||||
|
operator String? @db.VarChar(255)
|
||||||
|
xiangduifang String? @db.VarChar(255)
|
||||||
|
is_open String? @db.VarChar(255)
|
||||||
|
contract_amount String? @db.VarChar(255)
|
||||||
|
remarks String? @db.VarChar(255)
|
||||||
|
performance_start_time String? @db.VarChar(255)
|
||||||
|
hetong_yuanjian_saomiaojian_gaizhangban String? @db.Text
|
||||||
|
contract_attachment_scan String? @db.Text
|
||||||
|
dingding_purchase_approval_file String? @db.Text
|
||||||
|
creation_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_form_title String? @db.VarChar(255)
|
||||||
|
update_time String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model actual_payment {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
project_number String? @db.VarChar(255)
|
||||||
|
payment_date String? @db.VarChar(255)
|
||||||
|
payment_year String? @db.VarChar(255)
|
||||||
|
pingzhenghao String? @db.VarChar(255)
|
||||||
|
hangxiangmu_wenben String? @db.VarChar(255)
|
||||||
|
business_line String? @db.VarChar(255)
|
||||||
|
qimo_yueyuan String? @db.VarChar(255)
|
||||||
|
qimo_yueyuan_daxie String? @db.VarChar(255)
|
||||||
|
payment_method String? @db.VarChar(255)
|
||||||
|
gongyingshang_mingcheng String? @db.VarChar(255)
|
||||||
|
created_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_title String? @db.VarChar(255)
|
||||||
|
updated_time String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model expense_reimbursement {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
project_number String? @db.VarChar(255)
|
||||||
|
reimbursement_time String? @db.VarChar(255)
|
||||||
|
category String? @db.VarChar(255)
|
||||||
|
project_team String? @db.VarChar(255)
|
||||||
|
amount String? @db.VarChar(255)
|
||||||
|
sr_number String? @db.VarChar(255)
|
||||||
|
baoxiaoren String? @db.VarChar(255)
|
||||||
|
suoshu_zhangtao String? @db.VarChar(255)
|
||||||
|
baoxiao_yuefen String? @db.VarChar(255)
|
||||||
|
baoxiao_nianfen String? @db.VarChar(255)
|
||||||
|
expense_type String? @db.VarChar(255)
|
||||||
|
gongyingshang_guazhang_mingcheng String? @db.VarChar(255)
|
||||||
|
created_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_title String? @db.VarChar(255)
|
||||||
|
updated_time String? @db.VarChar(255)
|
||||||
|
xuhao String? @db.VarChar(255)
|
||||||
|
date String? @db.VarChar(255)
|
||||||
|
voucher_number String? @db.VarChar(255)
|
||||||
|
entry_number String? @db.VarChar(255)
|
||||||
|
summary String? @db.VarChar(255)
|
||||||
|
account_code String? @db.VarChar(255)
|
||||||
|
account_name String? @db.VarChar(255)
|
||||||
|
auxiliary_items String? @db.VarChar(1000)
|
||||||
|
currency String? @db.VarChar(255)
|
||||||
|
debit_original_currency String? @db.VarChar(255)
|
||||||
|
debit_local_currency String? @db.VarChar(255)
|
||||||
|
credit_original_currency String? @db.VarChar(255)
|
||||||
|
credit_local_currency String? @db.VarChar(255)
|
||||||
|
shared_voucher_number String? @db.VarChar(255)
|
||||||
|
expense_budget_item String? @db.VarChar(255)
|
||||||
|
cost_center String? @db.VarChar(255)
|
||||||
|
standard_name String? @db.VarChar(255)
|
||||||
|
expense_type_level1 String? @db.VarChar(255)
|
||||||
|
expense_type_level2 String? @db.VarChar(255)
|
||||||
|
expense_type_level3 String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model procurement_contract_list {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
initiator String? @db.VarChar(255)
|
||||||
|
contract_number String? @db.VarChar(255)
|
||||||
|
contract_name String? @db.VarChar(255)
|
||||||
|
lvyue_faqi_bumen String? @db.VarChar(255)
|
||||||
|
operator String? @db.VarChar(255)
|
||||||
|
xiangduifang String? @db.VarChar(255)
|
||||||
|
is_open String? @db.VarChar(255)
|
||||||
|
contract_amount String? @db.VarChar(255)
|
||||||
|
remarks String? @db.VarChar(255)
|
||||||
|
performance_start_time String? @db.VarChar(255)
|
||||||
|
hetong_yuanjian_saomiaojian_gaizhangban String? @db.Text
|
||||||
|
dingtalk_procurement_approval_form String? @db.Text
|
||||||
|
contract_attachment_scan String? @db.Text
|
||||||
|
creation_time String? @db.VarChar(255)
|
||||||
|
creator String? @db.VarChar(255)
|
||||||
|
approval_result String? @db.VarChar(255)
|
||||||
|
approval_status String? @db.VarChar(255)
|
||||||
|
update_time String? @db.VarChar(255)
|
||||||
|
creator_department String? @db.VarChar(255)
|
||||||
|
approval_form_title String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model invoice_void_record {
|
||||||
|
logid Int @default(autoincrement())
|
||||||
|
dataId String @db.VarChar(255)
|
||||||
|
applicationDepartment String? @db.VarChar(255)
|
||||||
|
xiang_mu_zu_bie String? @db.VarChar(255)
|
||||||
|
applicationDate String? @db.VarChar(255)
|
||||||
|
applicant String? @db.VarChar(255)
|
||||||
|
piao_hao String? @db.VarChar(255)
|
||||||
|
kai_piao_ri_qi String? @db.VarChar(255)
|
||||||
|
agreementType String? @db.VarChar(255)
|
||||||
|
zuo_fei_shi_you String? @db.VarChar(255)
|
||||||
|
cancelReason String? @db.VarChar(255)
|
||||||
|
invoice_amount_yuan String? @db.VarChar(255)
|
||||||
|
is_tax_inclusive String? @db.VarChar(255)
|
||||||
|
feeBelongingTime String? @db.VarChar(255)
|
||||||
|
customerCompanyName String? @db.VarChar(255)
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma String? @db.VarChar(255)
|
||||||
|
bankName String? @db.VarChar(255)
|
||||||
|
bankAccount String? @db.VarChar(255)
|
||||||
|
uploadedVoidInvoiceAttachment String? @db.Text
|
||||||
|
handwrittenSignatureAttachment String? @db.Text
|
||||||
|
projectNumber String? @db.VarChar(255)
|
||||||
|
remark String? @db.VarChar(255)
|
||||||
|
|
||||||
|
@@id([logid, dataId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model customer_management_info {
|
||||||
|
data_id String @id @db.VarChar(255)
|
||||||
|
customer_unit_name String @db.VarChar(500)
|
||||||
|
customer_type String? @db.VarChar(100)
|
||||||
|
created_at DateTime? @default(now()) @db.Timestamp(0)
|
||||||
|
updated_at DateTime? @default(now()) @db.Timestamp(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This view or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
view invoice_application_match_inner_view {
|
||||||
|
dataId String @db.VarChar(255)
|
||||||
|
invoiceJobNumber Int @default(0)
|
||||||
|
agreementType String? @db.VarChar(255)
|
||||||
|
approvalNumber String? @db.VarChar(255)
|
||||||
|
salesContractNumber String? @db.VarChar(255)
|
||||||
|
applicationDepartment String? @db.VarChar(255)
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan String? @db.VarChar(255)
|
||||||
|
is_multi_project_merge String? @db.VarChar(255)
|
||||||
|
is_single_contract_multi_customer String? @db.VarChar(255)
|
||||||
|
projectNumber String? @db.VarChar(255)
|
||||||
|
customerCompanyName String? @db.VarChar(255)
|
||||||
|
customerCompanyNameOther String? @db.VarChar(255)
|
||||||
|
piao_hao String? @db.VarChar(50)
|
||||||
|
bu_han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
applicant String? @db.VarChar(255)
|
||||||
|
business_content_du String? @db.VarChar(255)
|
||||||
|
uploadedContractAttachment String? @db.Text
|
||||||
|
uploadedSealedCostListDa String? @db.Text
|
||||||
|
uploadedSealedCostListDu String? @db.Text
|
||||||
|
invoiceType String? @db.VarChar(50)
|
||||||
|
invoiceCode Int? @db.UnsignedTinyInt
|
||||||
|
kai_piao_shi_you String? @db.VarChar(255)
|
||||||
|
fa_piao_bei_zhu_lan_tian_xie_nei_rong String? @db.Text
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma String? @db.VarChar(255)
|
||||||
|
bankName String? @db.VarChar(255)
|
||||||
|
bankAccount String? @db.VarChar(255)
|
||||||
|
invoice_type_enum Int?
|
||||||
|
kai_piao_ri_qi String? @db.VarChar(50)
|
||||||
|
hui_kuan_ri_qi String? @db.VarChar(255)
|
||||||
|
hui_kuan_jin_e String? @db.VarChar(255)
|
||||||
|
zhang_qi String? @db.VarChar(255)
|
||||||
|
unit_nature String? @db.VarChar(100)
|
||||||
|
hong_chong_piao_hao String? @db.VarChar(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This view or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
view invoice_application_match_view {
|
||||||
|
dataId String @db.VarChar(255)
|
||||||
|
invoiceJobNumber Int @default(0)
|
||||||
|
agreementType String? @db.VarChar(255)
|
||||||
|
approvalNumber String? @db.VarChar(255)
|
||||||
|
salesContractNumber String? @db.VarChar(255)
|
||||||
|
applicationDepartment String? @db.VarChar(255)
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan String? @db.VarChar(255)
|
||||||
|
is_multi_project_merge String? @db.VarChar(255)
|
||||||
|
is_single_contract_multi_customer String? @db.VarChar(255)
|
||||||
|
projectNumber String? @db.VarChar(255)
|
||||||
|
customerCompanyName String? @db.VarChar(255)
|
||||||
|
customerCompanyNameOther String? @db.VarChar(255)
|
||||||
|
piao_hao String? @db.VarChar(50)
|
||||||
|
bu_han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_da String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_da_du String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_duo String? @db.VarChar(255)
|
||||||
|
bu_han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
han_shui_jin_e_yuan_other String? @db.VarChar(255)
|
||||||
|
applicant String? @db.VarChar(255)
|
||||||
|
business_content_du String? @db.VarChar(255)
|
||||||
|
uploadedContractAttachment String? @db.Text
|
||||||
|
uploadedSealedCostListDa String? @db.Text
|
||||||
|
uploadedSealedCostListDu String? @db.Text
|
||||||
|
invoiceType String? @db.VarChar(50)
|
||||||
|
invoiceCode Int? @db.UnsignedTinyInt
|
||||||
|
kai_piao_shi_you String? @db.VarChar(255)
|
||||||
|
fa_piao_bei_zhu_lan_tian_xie_nei_rong String? @db.Text
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma String? @db.VarChar(255)
|
||||||
|
bankName String? @db.VarChar(255)
|
||||||
|
bankAccount String? @db.VarChar(255)
|
||||||
|
invoice_type_enum Int?
|
||||||
|
kai_piao_ri_qi String? @db.VarChar(50)
|
||||||
|
hui_kuan_ri_qi String? @db.VarChar(255)
|
||||||
|
hui_kuan_jin_e String? @db.VarChar(255)
|
||||||
|
zhang_qi String? @db.VarChar(255)
|
||||||
|
unit_nature String? @db.VarChar(100)
|
||||||
|
hong_chong_piao_hao String? @db.VarChar(50)
|
||||||
|
}
|
||||||
93
scripts/tmp_check_dj202500868.js
Normal file
93
scripts/tmp_check_dj202500868.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
function splitProjectNumbers(value) {
|
||||||
|
return String(value ?? '')
|
||||||
|
.split(/[,,、\s]+/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const project = 'DJ202500868';
|
||||||
|
const rows = await prisma.invoice_application_match_view.findMany({
|
||||||
|
where: { projectNumber: { contains: project } },
|
||||||
|
select: {
|
||||||
|
dataId: true,
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
projectNumber: true,
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan: true,
|
||||||
|
kai_piao_ri_qi: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const filtered = rows.filter((r) => splitProjectNumbers(r.projectNumber).includes(project));
|
||||||
|
const dataIds = [...new Set(filtered.map((r) => r.dataId).filter(Boolean))];
|
||||||
|
|
||||||
|
const details =
|
||||||
|
dataIds.length > 0
|
||||||
|
? await prisma.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: { in: dataIds },
|
||||||
|
project_number: project,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
dataid: true,
|
||||||
|
project_number: true,
|
||||||
|
invoice_amount: true,
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const mainByDataId = dataIds.map((id) => {
|
||||||
|
const arr = filtered.filter((r) => r.dataId === id);
|
||||||
|
const uniqueMainThirdParty = [...new Set(arr.map((r) => String(r.yuJiBaoXiaoDiSanFangChengBenYuan ?? '')).filter(Boolean))];
|
||||||
|
const detailThirdPartyRaw = details
|
||||||
|
.filter((d) => d.dataid === id)
|
||||||
|
.map((d) => String(d.yu_ji_bao_xiao_di_san_fang_cheng_ben ?? ''))
|
||||||
|
.filter((s) => s !== '');
|
||||||
|
return {
|
||||||
|
dataId: id,
|
||||||
|
mainCount: arr.length,
|
||||||
|
uniqueMainThirdParty,
|
||||||
|
detailRows: details.filter((d) => d.dataid === id).length,
|
||||||
|
detailThirdPartyRaw,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainDedupByInvoiceKey = new Map();
|
||||||
|
filtered.forEach((r) => {
|
||||||
|
const key = `${r.dataId || ''}||${r.invoiceJobNumber || ''}`;
|
||||||
|
if (!mainDedupByInvoiceKey.has(key)) {
|
||||||
|
mainDedupByInvoiceKey.set(key, Number(r.yuJiBaoXiaoDiSanFangChengBenYuan || 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const mainTotal = [...mainDedupByInvoiceKey.values()].reduce((s, n) => s + n, 0);
|
||||||
|
|
||||||
|
const detailTotal = details.reduce((s, r) => s + Number(r.yu_ji_bao_xiao_di_san_fang_cheng_ben || 0), 0);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
project,
|
||||||
|
filteredCount: filtered.length,
|
||||||
|
dataIds,
|
||||||
|
mainByDataId,
|
||||||
|
mainTotal,
|
||||||
|
detailTotal,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
92
scripts/tmp_check_jn202600186.js
Normal file
92
scripts/tmp_check_jn202600186.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
function splitProjectNumbers(value) {
|
||||||
|
return String(value ?? '')
|
||||||
|
.split(/[,,、\s]+/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const project = 'JN202600186';
|
||||||
|
const mainRows = await prisma.invoice_application_match_view.findMany({
|
||||||
|
where: { projectNumber: { contains: project } },
|
||||||
|
select: {
|
||||||
|
dataId: true,
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
projectNumber: true,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: true,
|
||||||
|
han_shui_jin_e_yuan_duo: true,
|
||||||
|
bu_han_shui_jin_e_yuan_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_other: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da_du: true,
|
||||||
|
kai_piao_ri_qi: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredRows = mainRows.filter((r) => splitProjectNumbers(r.projectNumber).includes(project));
|
||||||
|
const ids = [...new Set(filteredRows.map((r) => r.dataId).filter(Boolean))];
|
||||||
|
|
||||||
|
const detailRows =
|
||||||
|
ids.length > 0
|
||||||
|
? await prisma.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: { in: ids },
|
||||||
|
project_number: project,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
dataid: true,
|
||||||
|
project_number: true,
|
||||||
|
invoice_amount: true,
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const summaryByDataId = ids.map((dataId) => {
|
||||||
|
const mainForDataId = filteredRows.filter((r) => r.dataId === dataId);
|
||||||
|
const detailForDataId = detailRows.filter((r) => r.dataid === dataId);
|
||||||
|
const duoBuHan = Number(mainForDataId.find((r) => r.bu_han_shui_jin_e_yuan_duo)?.bu_han_shui_jin_e_yuan_duo || 0);
|
||||||
|
const duoHan = Number(mainForDataId.find((r) => r.han_shui_jin_e_yuan_duo)?.han_shui_jin_e_yuan_duo || 0);
|
||||||
|
const detailHanShuiSum = detailForDataId.reduce((s, r) => s + Number(r.invoice_amount || 0), 0);
|
||||||
|
const calcBuHan = duoHan > 0 ? (detailHanShuiSum * duoBuHan) / duoHan : detailHanShuiSum;
|
||||||
|
return {
|
||||||
|
dataId,
|
||||||
|
mainRowCount: mainForDataId.length,
|
||||||
|
detailRowCount: detailForDataId.length,
|
||||||
|
detailHanShuiSum,
|
||||||
|
duoBuHan,
|
||||||
|
duoHan,
|
||||||
|
calcBuHan,
|
||||||
|
detailSamples: detailForDataId.slice(0, 5),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalBuHan = summaryByDataId.reduce((s, r) => s + r.calcBuHan, 0);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
project,
|
||||||
|
filteredCount: filteredRows.length,
|
||||||
|
ids,
|
||||||
|
summaryByDataId,
|
||||||
|
totalBuHan,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
325
src/CONSTANTS.ts
Normal file
325
src/CONSTANTS.ts
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// 这里存放项目的常量配置
|
||||||
|
// oa 正式环境东风
|
||||||
|
// oa 测试环境 TODO 待建立
|
||||||
|
// 协议类型常量
|
||||||
|
|
||||||
|
export const INVOICE_TYPES = {
|
||||||
|
NORMAL: 0,
|
||||||
|
VOID_CURRENT_MONTH: 1,
|
||||||
|
VOID_REDEEM: 2
|
||||||
|
}
|
||||||
|
// 发票类型编码
|
||||||
|
export const INVOICE_TYPE_CODE = {
|
||||||
|
[INVOICE_TYPES.NORMAL]: '0',
|
||||||
|
[INVOICE_TYPES.VOID_CURRENT_MONTH]: '1',
|
||||||
|
[INVOICE_TYPES.VOID_REDEEM]: '2'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INVOICE_TYPE_NAME = {
|
||||||
|
'普通票': INVOICE_TYPES.NORMAL,
|
||||||
|
'作废当月': INVOICE_TYPES.VOID_CURRENT_MONTH,
|
||||||
|
'作废红冲': INVOICE_TYPES.VOID_REDEEM
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const AGREEMENT_TYPES = {
|
||||||
|
LARGE: 'large',
|
||||||
|
INDEPENDENT: 'independent',
|
||||||
|
LARGE_INDEPENDENT: 'large_independent',
|
||||||
|
OTHER: 'other'
|
||||||
|
}
|
||||||
|
// 协议类型编码
|
||||||
|
export const AGREEMENT_TYPE_CODE = {
|
||||||
|
[AGREEMENT_TYPES.LARGE]: '1',
|
||||||
|
[AGREEMENT_TYPES.INDEPENDENT]: '2',
|
||||||
|
[AGREEMENT_TYPES.LARGE_INDEPENDENT]: '3',
|
||||||
|
[AGREEMENT_TYPES.OTHER]: '4'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AGREEMENT_TYPE_NAME = {
|
||||||
|
'大协议(客户服务室专用)': AGREEMENT_TYPES.LARGE,//大协议改为大协议(客户服务室专用)
|
||||||
|
'独立协议': AGREEMENT_TYPES.INDEPENDENT,
|
||||||
|
'大协议+独立协议(客户服务室专用)': AGREEMENT_TYPES.LARGE_INDEPENDENT,//大协议+独立协议改为大协议+独立协议(客户服务室专用)
|
||||||
|
'其它': AGREEMENT_TYPES.OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 创建一个从协议类型编码到协议类型名称的映射
|
||||||
|
export const AGREEMENT_CODE_TO_NAME = Object.fromEntries(
|
||||||
|
Object.entries(AGREEMENT_TYPE_NAME).map(([name, code]) => [AGREEMENT_TYPE_CODE[code], name])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AGREEMENT_CODE_TO_TYPE = Object.fromEntries(
|
||||||
|
Object.entries(AGREEMENT_TYPE_CODE).map(([type, code]) => [code, type])
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// 字段映射配置
|
||||||
|
export const FIELD_MAPPINGS = {
|
||||||
|
purchase_contract_execution_maintenance: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'发起人': 'initiator',
|
||||||
|
'合同编号': 'contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'履约发起部门': 'lvyue_faqi_bumen',
|
||||||
|
'经办人': 'operator',
|
||||||
|
'相对方': 'xiangduifang',
|
||||||
|
'是否开口': 'is_open',
|
||||||
|
'合同总额': 'contract_amount',
|
||||||
|
'备注': 'remarks',
|
||||||
|
'履约开始时间': 'performance_start_time',
|
||||||
|
'合同原件扫描件(盖章版)': 'hetong_yuanjian_saomiaojian_gaizhangban',
|
||||||
|
'钉钉采购审批单': 'dingding_purchase_approval_file',
|
||||||
|
'合同附件扫描件': 'contract_attachment_scan',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed',
|
||||||
|
},
|
||||||
|
'purchase_contract_order_management': {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'采购合同编号': 'purchase_contract_number',
|
||||||
|
'采购用途': 'purchase_purpose',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'项目名称': 'project_name',
|
||||||
|
'关联销售合同编号': 'associated_sales_contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'采购审批单号(请先填写合同编号)': 'purchase_approval_number',
|
||||||
|
//'采购订单编号(请先填写合同编号)': 'purchase_approval_number',
|
||||||
|
'合同名称(***请再次确认合同名称***)': 'purchase_contract_name',
|
||||||
|
// '采购合同名称(***请再次确认合同名称***)': 'purchase_contract_name',
|
||||||
|
'相对方': 'xiang_dui_fang',
|
||||||
|
'合同类别(是否开口)': 'he_tong_lei_bie',
|
||||||
|
'合同总金额(含税)': 'he_tong_zong_jin_e_HanShui',
|
||||||
|
'剩余金额(含税)': 'sheng_yu_jin_e_HanShui',
|
||||||
|
'合同剩余金额提醒': 'he_tong_sheng_yu_jin_e_ti_xing',
|
||||||
|
'本次订单名称': 'ben_ci_ding_dan_ming_cheng',
|
||||||
|
'本次订单金额(含税)': 'ben_ci_ding_dan_jin_e_HanShui',
|
||||||
|
'本次订单履约开始时间': 'ben_ci_ding_dan_lu_yue_kai_shi_shi_jian',
|
||||||
|
'本次订单履约结束时间': 'ben_ci_ding_dan_lu_yue_jie_shu_shi_jian',
|
||||||
|
'本次订单发起部门': 'ben_ci_ding_dan_fa_qi_bu_men',
|
||||||
|
'本次订单发起人': 'ben_ci_ding_dan_fa_qi_ren',
|
||||||
|
'本次订单发起时间': 'ben_ci_ding_dan_fa_qi_shi_jian',
|
||||||
|
'本次订单文本(多文件请压缩后上传)': 'ben_ci_ding_dan_wen_ben',
|
||||||
|
'本次订单关联合同附件': 'ben_ci_ding_dan_guan_lian_he_tong_fujian',
|
||||||
|
'审批编号': 'approval_number',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'current_responsible_person',
|
||||||
|
'审批结果': 'approval_result',
|
||||||
|
'审批状态': 'approval_status',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed'
|
||||||
|
},
|
||||||
|
"dingding_oa_invoice_application": {
|
||||||
|
'数据id': 'dataId',
|
||||||
|
'开票类型': 'agreementType',
|
||||||
|
'申请日期': 'applicationDate',
|
||||||
|
'申请人': 'applicant',
|
||||||
|
'申请部门': 'applicationDepartment',
|
||||||
|
'业务组名称': 'xiang_mu_zu_bie',//项目组别改为业务组名称
|
||||||
|
'预计报销第三方成本(不含税,元)': 'yuJiBaoXiaoDiSanFangChengBenYuan',
|
||||||
|
'业务开始时间': 'businessStartTime',
|
||||||
|
'业务结束时间': 'businessEndTime',
|
||||||
|
'销售合同编号': 'contractNumber',// 因为这里是关联的数据怀疑是 别名 --展示名称前面有销售
|
||||||
|
'销售合同名称': 'contractName',// 因为这里是关联的数据怀疑是 别名--展示名称前面有销售
|
||||||
|
'上传合同及相关附件': 'uploadedContractAttachment',
|
||||||
|
'上传合同及相关附件(含费用清单)': 'uploadedContractAttachment',
|
||||||
|
'项目编号': 'projectNumber',
|
||||||
|
'金额是否含税': 'is_tax_inclusive',//是否含税改为金额是否含税
|
||||||
|
'开票税率': 'invoice_tax_rate',
|
||||||
|
'是否多项目合并': 'is_multi_project_merge',
|
||||||
|
'上传费用清单': 'uploadedSealedCostListDa',//上传盖章费用清单(大)改为上传费用清单
|
||||||
|
'请上传费用清单': 'uploadedSealedCostListDa',//上传盖章费用清单(大)改为上传费用清单
|
||||||
|
'上传盖章费用清单(独)': 'uploadedSealedCostListDu',//上传盖章费用清单(独)弃用
|
||||||
|
'付款单位名称': 'customerCompanyName',//客户单位名称改为付款单位名称
|
||||||
|
'付款单位名称2': 'customerCompanyName',//客户单位名称改为付款单位名称
|
||||||
|
'付款单位名称3': 'customerCompanyName',//客户单位名称改为付款单位名称
|
||||||
|
'客户单位名称(其他)': 'customerCompanyNameOther',
|
||||||
|
'统一社会信用代码': 'tong_yi_she_hui_xin_yong_dai_ma',
|
||||||
|
'开户行名称': 'bankName',
|
||||||
|
'银行账号': 'bankAccount',
|
||||||
|
'付款人': 'payee',//新增付款人
|
||||||
|
'付款人2': 'payee',//新增付款人
|
||||||
|
'付款人3': 'payee',//新增付款人
|
||||||
|
'发票税目': 'invoice_tax_category',
|
||||||
|
'业务内容(摘要)': 'business_content_du',
|
||||||
|
'开票金额明细(大)': 'invoice_amount_yuan_da',//开票金额明细(大)
|
||||||
|
// "开票金额(元,不含税)": 'invoice_amount_yuan_bu_han_shui',//开票金额(元,不含税)
|
||||||
|
// "付款单位名称2": 'customerCompanyName2',//新增付款单位名称2
|
||||||
|
// "付款单位名称3": 'customerCompanyName3',//新增付款单位名称3
|
||||||
|
// "付款人3": 'payee3',//新增付款人3
|
||||||
|
// "付款人2": 'payee2',//新增付款人2
|
||||||
|
// "项目名称1": 'projectName1',//新增项目名称1
|
||||||
|
'开票金额(元,不含税)':'invoice_amount_yuan',
|
||||||
|
'开票金额(元)独': 'invoice_amount_yuan_du',
|
||||||
|
'开票金额(元)其他': 'invoice_amount_yuan_other',
|
||||||
|
'不含税金额(元)独': 'bu_han_shui_jin_e_yuan_du',
|
||||||
|
'不含税金额(元)独(大写)': 'bu_han_shui_jin_e_yuan_du_da_xie',
|
||||||
|
'含税金额(元)独': 'han_shui_jin_e_yuan_du',
|
||||||
|
'含税金额(元)独(大写)': 'han_shui_jin_e_yuan_du_da_xie',
|
||||||
|
'不含税金额(元)其他': 'bu_han_shui_jin_e_yuan_other',
|
||||||
|
'不含税金额(元)其他(大写)': 'bu_han_shui_jin_e_yuan_other_da_xie',
|
||||||
|
'含税金额(元)其他': 'han_shui_jin_e_yuan_other',
|
||||||
|
'含税金额(元)其他(大写)': 'han_shui_jin_e_yuan_other_da_xie',
|
||||||
|
'不含税金额(元)多': 'bu_han_shui_jin_e_yuan_duo',
|
||||||
|
'不含税金额(元)多项': 'bu_han_shui_jin_e_yuan_duo',
|
||||||
|
'不含税金额(元)多(大写)': 'bu_han_shui_jin_e_yuan_duo_da_xie',
|
||||||
|
'含税金额(元)多': 'han_shui_jin_e_yuan_duo',
|
||||||
|
'含税金额(元)多项': 'han_shui_jin_e_yuan_duo',
|
||||||
|
'含税金额(元)多(大写)': 'han_shui_jin_e_yuan_duo_da_xie',
|
||||||
|
'不含税金额(元)大': 'bu_han_shui_jin_e_yuan_da',
|
||||||
|
'含税总金额(元)大': 'han_shui_zong_jin_e_yuan_da',
|
||||||
|
'不含税金额(元)(大+独)': 'bu_han_shui_jin_e_yuan_da_du',
|
||||||
|
'不含税金额(元)(大+独)(大写)': 'bu_han_shui_jin_e_yuan_da_du_da_xie',
|
||||||
|
'含税总金额(元)(大+独)': 'han_shui_zong_jin_e_yuan_da_du',
|
||||||
|
'含税总金额(元)(大+独)(大写)': 'han_shui_zong_jin_e_yuan_da_du_da_xie',
|
||||||
|
"不含税金额(元)多家": 'bu_han_shui_jin_e_yuan_duo',//不含税金额(元)多项改为多家
|
||||||
|
"含税金额(元)多家": 'han_shui_jin_e_yuan_duo',//含税金额(元)多项改为多家
|
||||||
|
'审批编号': 'approvalNumber',
|
||||||
|
'创建时间': 'creationTime',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'currentResponsiblePerson',
|
||||||
|
'审批结果': 'approvalResult',
|
||||||
|
'审批状态': 'approvalStatus',
|
||||||
|
'更新时间': 'updateTime',
|
||||||
|
'创建人部门': 'creatorDepartment',
|
||||||
|
'审批单标题': 'approvalFormTitle',
|
||||||
|
'历史审批人': 'historicalApprovers',
|
||||||
|
'耗时(时:分:秒)': 'timeConsumed',
|
||||||
|
'审批记录': 'approvalRecord',
|
||||||
|
"开票金额明细": 'BigInvoiceAmountList',
|
||||||
|
'多项目合并开票请添加': 'multiProjectMergeInvoice',//多项目合并开票请添加项目名称改为多项目合并开票请添加
|
||||||
|
// "多项目合并开票请添加项目名称": 'multiProjectMergeInvoice',
|
||||||
|
"[\"业务开始时间\",\"业务结束时间\"]": 'businessStartTime-BusinessEndTime',
|
||||||
|
|
||||||
|
// 新增字段映射
|
||||||
|
'开票事由': 'kai_piao_shi_you',
|
||||||
|
'发票备注栏填写内容': 'fa_piao_bei_zhu_lan_tian_xie_nei_rong',
|
||||||
|
'票据类型': 'invoiceType',
|
||||||
|
'发票开具类别': 'invoice_tax_category',//发票开具类别数据库表中发票类目
|
||||||
|
'是否单合同多家单位开票': 'is_single_contract_multi_customer',//是否单合同多家单位开票
|
||||||
|
'单合同多家单位开票清单': 'single_contract_multi_customer_invoice_list',//单合同多家单位开票清单
|
||||||
|
},
|
||||||
|
"invoice_void_record": {
|
||||||
|
'数据id': 'dataId',
|
||||||
|
'所在部门': 'applicationDepartment',
|
||||||
|
'申请业务组': 'xiang_mu_zu_bie',
|
||||||
|
'申请日期': 'applicationDate',
|
||||||
|
'申请人': 'applicant',
|
||||||
|
'发票号码': 'piao_hao',
|
||||||
|
'开票日期': 'kai_piao_ri_qi',
|
||||||
|
'协议类型': 'agreementType',
|
||||||
|
'作废事由': 'zuo_fei_shi_you',
|
||||||
|
'作废原因': 'cancelReason',
|
||||||
|
'发票金额': 'invoice_amount_yuan',
|
||||||
|
'金额是否含税': 'is_tax_inclusive',
|
||||||
|
'费用所属时间': 'feeBelongingTime',
|
||||||
|
'付款单位名称': 'customerCompanyName',
|
||||||
|
'付款单位纳税人识别号': 'tong_yi_she_hui_xin_yong_dai_ma',
|
||||||
|
'付款单位开户行': 'bankName',
|
||||||
|
'付款单位开户账号': 'bankAccount',
|
||||||
|
'上传作废发票附件': 'uploadedVoidInvoiceAttachment',
|
||||||
|
'手写签名': 'handwrittenSignatureAttachment',
|
||||||
|
'项目编号': 'projectNumber',
|
||||||
|
'备注信息': 'remark'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DATA_ID_FIELD_MAPPINGS = {
|
||||||
|
purchase_contract_execution_maintenance: 'data_id',
|
||||||
|
purchase_contract_order_management: 'data_id',
|
||||||
|
dingding_oa_invoice_application: 'dataId',
|
||||||
|
invoice_void_record: 'dataId',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 受支持的表配置
|
||||||
|
export const SUPPORTED_TABLES = {
|
||||||
|
purchase_contract_execution_maintenance: '采购合同执行维护表',
|
||||||
|
purchase_contract_order_management: '采购合同订单管理',
|
||||||
|
dingding_oa_invoice_application: '钉钉OA发票申请表',
|
||||||
|
invoice_void_record: '发票作废记录表'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONSTANTS = {
|
||||||
|
// 示例配置
|
||||||
|
// key: 'value'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const XIANGMU_ZUBIE_MAP: {
|
||||||
|
[key: string]: string
|
||||||
|
} = {
|
||||||
|
'运营室': '运营',
|
||||||
|
'人员招聘项目组': '招聘',
|
||||||
|
'总务服务项目组': '总务',
|
||||||
|
'薪酬核算项目组': '薪酬',
|
||||||
|
'退休衔接服务项目组': '退休衔接',
|
||||||
|
'计划核定项目组': '统筹-计核',
|
||||||
|
'老干部服务项目组': '老干部服务',
|
||||||
|
'社保业务项目组': '统筹-社保',
|
||||||
|
'医保业务项目组': '统筹-医保',
|
||||||
|
'统筹支持项目组': '统筹-支持',
|
||||||
|
'人员入转离项目组': '入转离',
|
||||||
|
'人才评鉴项目组': '评鉴',
|
||||||
|
'会务服务项目组': '培训-会务',
|
||||||
|
'国际化服务项目组': '国际化服务',
|
||||||
|
'管理咨询项目组': '管理咨询',
|
||||||
|
'客户服务室': '客户服务',
|
||||||
|
'福利业务项目组': '福利',
|
||||||
|
'法务合规中心': '法务',
|
||||||
|
'数字化与交付项目组': '培训-数交',
|
||||||
|
'科技培训项目组': '培训-科技',
|
||||||
|
'经营管理项目组': '培训-经管',
|
||||||
|
'技能培训项目组': '培训-技能',
|
||||||
|
'党建培训项目组': '党校-党建',
|
||||||
|
'数字化室': '数字化',
|
||||||
|
'武汉站': '武汉站',
|
||||||
|
'十堰站': '十堰站',
|
||||||
|
'襄阳站': '襄阳站',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ABBR_ZUBIE_MAP: {
|
||||||
|
[key: string]: string
|
||||||
|
} = {
|
||||||
|
YY: '运营室',
|
||||||
|
ZP: '人员招聘项目组',
|
||||||
|
ZW: '总务服务项目组',
|
||||||
|
XC: '薪酬核算项目组',
|
||||||
|
TX: '退休衔接服务项目组',
|
||||||
|
TC: '统筹支持项目组',
|
||||||
|
RZL: '人员入转离项目组',
|
||||||
|
PJ: '人才评鉴项目组',
|
||||||
|
HW: '会务服务项目组',
|
||||||
|
GJ: '国际化服务项目组',
|
||||||
|
GZ: '管理咨询项目组',
|
||||||
|
KF: '客户服务室',
|
||||||
|
FL: '福利业务项目组',
|
||||||
|
FW: '法务合规中心',
|
||||||
|
SJ: '数字化与交付项目组',
|
||||||
|
KJ: '科技培训项目组',
|
||||||
|
JG: '经营管理项目组',
|
||||||
|
JN: '技能培训项目组',
|
||||||
|
DJ: '党建培训项目组',
|
||||||
|
JH: '计划核定项目组',
|
||||||
|
LGB: '老干部服务项目组'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 新增:票据类型的双向映射(中文 <-> 代码)
|
||||||
|
export const INVOICE_CODE: Record<string, number> = {
|
||||||
|
'增值税专用发票': 1,
|
||||||
|
'增值税普通发票': 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INVOICE_NAME: Record<number, string> = {
|
||||||
|
1: '增值税专用发票',
|
||||||
|
2: '增值税普通发票',
|
||||||
|
};
|
||||||
186
src/api/dingding/auth.ts
Normal file
186
src/api/dingding/auth.ts
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// This file is auto-generated, don't edit it
|
||||||
|
import Util from '@alicloud/tea-util';
|
||||||
|
import dingtalkoauth2_1_0, * as $dingtalkoauth2_1_0 from '@alicloud/dingtalk/oauth2_1_0';
|
||||||
|
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
|
||||||
|
import { logger } from '../winston/logger';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default class AuthClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Token 初始化账号Client
|
||||||
|
* @return Client
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static createClient(): dingtalkoauth2_1_0 {
|
||||||
|
let config = new $OpenApi.Config({});
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
return new dingtalkoauth2_1_0(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getAccessToken(): Promise<string> {
|
||||||
|
let client = AuthClient.createClient();
|
||||||
|
// 测试环境
|
||||||
|
// let getAccessTokenRequest = new $dingtalkoauth2_1_0.GetAccessTokenRequest({
|
||||||
|
// appKey: "dingjrkdm6lqfebrwrhs",
|
||||||
|
// appSecret: "oxYwPO9F6-_TbjI-uKFpO1__Qc3jRPQ5MSInBvei2r6rscg_gZexbAqi-QR6ZNCz",
|
||||||
|
// });
|
||||||
|
// 正式环境
|
||||||
|
let getAccessTokenRequest = new $dingtalkoauth2_1_0.GetAccessTokenRequest({
|
||||||
|
appKey: process.env.DINGDING_APP_KEY,//"ding3fdr9ulnhzlvnznb",
|
||||||
|
appSecret: process.env.DINGDING_APP_SECRET,// "Jk9Ph6iQ0gmkSxjOTZ7Sx3q_Z9HfJ97SbHoB2M6FFTNBrXqXAg-h1npfyAtznB7i",
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await client.getAccessToken(getAccessTokenRequest);
|
||||||
|
console.log("res", res)
|
||||||
|
console.log("拿到钉钉的accessToken", res?.body?.accessToken);
|
||||||
|
return res?.body?.accessToken;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("AuthClient : getAccessToken", error)
|
||||||
|
logger.error("AuthClient : getAccessToken", error)
|
||||||
|
if (!Util.empty(error.code) && !Util.empty(error.message)) {
|
||||||
|
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// GetAccessTokenResponse {
|
||||||
|
// headers: {
|
||||||
|
// server: 'DingTalk/1.0.0',
|
||||||
|
// date: 'Mon, 30 Jun 2025 07:00:10 GMT',
|
||||||
|
// 'content-type': 'application/json;charset=utf-8',
|
||||||
|
// 'content-length': '66',
|
||||||
|
// connection: 'keep-alive',
|
||||||
|
// 'access-control-allow-origin': '*',
|
||||||
|
// 'access-control-expose-headers': '*',
|
||||||
|
// 'x-acs-request-id': '30D76DD0-4583-7A5A-A1F2-0846EEC1922D',
|
||||||
|
// 'x-acs-trace-id': 'e8076fdcf17402c1f1557baf1cec4b39',
|
||||||
|
// etag: '6F3iZV6GzSa1IL6X9PhSUVQ6',
|
||||||
|
// 'access-control-allow-headers': 'X-Requested-With, X-Sequence, _aop_secret, _aop_signature, x-acs-dingtalk-access-token'
|
||||||
|
// },
|
||||||
|
// statusCode: 200,
|
||||||
|
// body: GetAccessTokenResponseBody {
|
||||||
|
// accessToken: 'e12b5b336e103db2bca3bf6f120cf071',
|
||||||
|
// expireIn: 7200
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ----
|
||||||
|
// {
|
||||||
|
// headers: {
|
||||||
|
// server: 'DingTalk/1.0.0',
|
||||||
|
// date: 'Mon, 30 Jun 2025 09:21:30 GMT',
|
||||||
|
// 'content-type': 'application/json;charset=utf-8',
|
||||||
|
// 'content-length': '245',
|
||||||
|
// connection: 'keep-alive',
|
||||||
|
// 'access-control-allow-origin': '*',
|
||||||
|
// 'access-control-expose-headers': '*',
|
||||||
|
// 'x-acs-request-id': 'D08BBF0B-0C7A-777D-BEE0-E2481030660E',
|
||||||
|
// 'x-acs-trace-id': '4dd951ff3fdb1b880d8773ab8db4cc38',
|
||||||
|
// etag: '2sqH6LVyqyanwQEcOE52W+g5',
|
||||||
|
// 'access-control-allow-headers': 'X-Requested-With, X-Sequence, _aop_secret, _aop_signature, x-acs-dingtalk-access-token'
|
||||||
|
// },
|
||||||
|
// statusCode: 200,
|
||||||
|
// body: ListProcessInstanceIdsResponseBody {
|
||||||
|
// result: ListProcessInstanceIdsResponseBodyResult {
|
||||||
|
// list: [Array],
|
||||||
|
// nextToken: '2'
|
||||||
|
// },
|
||||||
|
// success: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "headers": {
|
||||||
|
// "server": "DingTalk/1.0.0",
|
||||||
|
// "date": "Tue, 01 Jul 2025 01:42:36 GMT",
|
||||||
|
// "content-type": "application/json;charset=utf-8",
|
||||||
|
// "content-length": "1384",
|
||||||
|
// "connection": "keep-alive",
|
||||||
|
// "vary": "Accept-Encoding, Accept-Encoding",
|
||||||
|
// "access-control-allow-origin": "*",
|
||||||
|
// "access-control-expose-headers": "*",
|
||||||
|
// "x-acs-request-id": "B76B8B3D-19E2-7274-8511-8BE66A6BE486",
|
||||||
|
// "x-acs-trace-id": "d271717bd4706e5495c7bec3fe226723",
|
||||||
|
// "etag": "1uyNMfzsmzilvk+sq7asEUw0",
|
||||||
|
// "access-control-allow-headers": "X-Requested-With, X-Sequence, _aop_secret, _aop_signature, x-acs-dingtalk-access-token"
|
||||||
|
// },
|
||||||
|
// "statusCode": 200,
|
||||||
|
// "body": {
|
||||||
|
// "result": {
|
||||||
|
// "attachedProcessInstanceIds": [
|
||||||
|
|
||||||
|
// ],
|
||||||
|
// "bizAction": "NONE",
|
||||||
|
// "bizData": "{}",
|
||||||
|
// "businessId": "202506301703000580140",
|
||||||
|
// "createTime": "2025-06-30T17:03Z",
|
||||||
|
// "formComponentValues": [
|
||||||
|
// {
|
||||||
|
// "bizAlias": "",
|
||||||
|
// "componentType": "TextField",
|
||||||
|
// "id": "TextField-K2AD4O5B",
|
||||||
|
// "name": "测试表单1",
|
||||||
|
// "value": "<22>🐶🐩"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "bizAlias": "",
|
||||||
|
// "componentType": "TextField",
|
||||||
|
// "id": "TextField_RJYCNJG9I680",
|
||||||
|
// "name": "测试表单2",
|
||||||
|
// "value": "🐕🐶🐶"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "componentType": "NumberField",
|
||||||
|
// "id": "NumberField_1I5DKSPC42GW0",
|
||||||
|
// "name": "数字输入框",
|
||||||
|
// "value": "234565432"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "componentType": "DDDateField",
|
||||||
|
// "id": "DDDateField_1JRPIPARZUKG0",
|
||||||
|
// "name": "日期",
|
||||||
|
// "value": "2025-07-25"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "operationRecords": [
|
||||||
|
// {
|
||||||
|
// "date": "2025-06-30T17:03Z",
|
||||||
|
// "images": [
|
||||||
|
|
||||||
|
// ],
|
||||||
|
// "result": "NONE",
|
||||||
|
// "showName": "提交申请",
|
||||||
|
// "type": "START_PROCESS_INSTANCE",
|
||||||
|
// "userId": "manager6697"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "originatorDeptId": "-1",
|
||||||
|
// "originatorDeptName": "猫咪食品厂",
|
||||||
|
// "originatorUserId": "manager6697",
|
||||||
|
// "result": "",
|
||||||
|
// "status": "RUNNING",
|
||||||
|
// "tasks": [
|
||||||
|
// {
|
||||||
|
// "activityId": "1918_5cd3",
|
||||||
|
// "createTime": "2025-06-30T17:03Z",
|
||||||
|
// "mobileUrl": "aflow.dingtalk.com?procInsId=c73YooUSTFW49EIXKjCPWA10021751274238&taskId=94657860987&businessId=202506301703000580140",
|
||||||
|
// "pcUrl": "aflow.dingtalk.com?procInsId=c73YooUSTFW49EIXKjCPWA10021751274238&taskId=94657860987&businessId=202506301703000580140",
|
||||||
|
// "result": "NONE",
|
||||||
|
// "status": "RUNNING",
|
||||||
|
// "taskId": 94657860987,
|
||||||
|
// "userId": "manager6697"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "title": "yatsov提交的数字化科室-测试表单"
|
||||||
|
// },
|
||||||
|
// "success": "true"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
50
src/api/dingding/download.ts
Normal file
50
src/api/dingding/download.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// This file is auto-generated, don't edit it
|
||||||
|
import Util,{ RuntimeOptions } from '@alicloud/tea-util';
|
||||||
|
import dingtalkworkflow_1_0, * as $dingtalkworkflow_1_0 from '@alicloud/dingtalk/workflow_1_0';
|
||||||
|
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
|
||||||
|
import { logger } from '../winston/logger';
|
||||||
|
|
||||||
|
|
||||||
|
export default class DownLoadClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Token 初始化账号Client
|
||||||
|
* @return Client
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static createClient(): dingtalkworkflow_1_0 {
|
||||||
|
let config = new $OpenApi.Config({ });
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
return new dingtalkworkflow_1_0(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getFileURL({
|
||||||
|
processInstanceId,
|
||||||
|
fileId,
|
||||||
|
xAcsDingtalkAccessToken
|
||||||
|
}:{
|
||||||
|
processInstanceId: string,
|
||||||
|
fileId: string,
|
||||||
|
xAcsDingtalkAccessToken: string
|
||||||
|
}): Promise<void> {
|
||||||
|
let client = DownLoadClient.createClient();
|
||||||
|
let grantProcessInstanceForDownloadFileHeaders = new $dingtalkworkflow_1_0.GrantProcessInstanceForDownloadFileHeaders({ });
|
||||||
|
grantProcessInstanceForDownloadFileHeaders.xAcsDingtalkAccessToken = xAcsDingtalkAccessToken;
|
||||||
|
let grantProcessInstanceForDownloadFileRequest = new $dingtalkworkflow_1_0.GrantProcessInstanceForDownloadFileRequest({
|
||||||
|
processInstanceId,
|
||||||
|
fileId,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
return client.grantProcessInstanceForDownloadFileWithOptions(grantProcessInstanceForDownloadFileRequest, grantProcessInstanceForDownloadFileHeaders, new RuntimeOptions({ }));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("DownLoadClient : getFileURL", err)
|
||||||
|
logger.error("DownLoadClient : getFileURL", err)
|
||||||
|
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||||
|
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
src/api/dingding/formInstances.ts
Normal file
46
src/api/dingding/formInstances.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// This file is auto-generated, don't edit it
|
||||||
|
import Util,{ RuntimeOptions } from '@alicloud/tea-util';
|
||||||
|
import dingtalkworkflow_1_0, * as $dingtalkworkflow_1_0 from '@alicloud/dingtalk/workflow_1_0';
|
||||||
|
import dingtalkoauth2_1_0, * as $dingtalkoauth2_1_0 from '@alicloud/dingtalk/oauth2_1_0';
|
||||||
|
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
|
||||||
|
export default class FormInstancesClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Token 初始化账号Client
|
||||||
|
* @return Client
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static createClient(): dingtalkworkflow_1_0 {
|
||||||
|
let config = new $OpenApi.Config({});
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
return new dingtalkworkflow_1_0(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getFormInstances(args: {
|
||||||
|
dingtalkAccessToken: string;
|
||||||
|
nextToken: string;
|
||||||
|
maxResults: number;
|
||||||
|
appUuid: string;
|
||||||
|
formCode: string;
|
||||||
|
}) {
|
||||||
|
let client = FormInstancesClient.createClient();
|
||||||
|
let premiumGetFormInstancesHeaders = new $dingtalkworkflow_1_0.PremiumGetFormInstancesHeaders({});
|
||||||
|
premiumGetFormInstancesHeaders.xAcsDingtalkAccessToken = args.dingtalkAccessToken;
|
||||||
|
let premiumGetFormInstancesRequest = new $dingtalkworkflow_1_0.PremiumGetFormInstancesRequest({
|
||||||
|
nextToken: args.nextToken,//'100010',
|
||||||
|
maxResults: args.maxResults,//100,
|
||||||
|
appUuid: args.appUuid,//'SWAPP-dacdsa-example',
|
||||||
|
formCode: args.formCode,//'PROC-daccea-example',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
return client.premiumGetFormInstancesWithOptions(premiumGetFormInstancesRequest, premiumGetFormInstancesHeaders, new RuntimeOptions({}));
|
||||||
|
} catch (err) {
|
||||||
|
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||||
|
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
88
src/api/dingding/oa.ts
Normal file
88
src/api/dingding/oa.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
// https://open.dingtalk.com/document/orgapp/obtain-an-approval-list-of-instance-ids
|
||||||
|
|
||||||
|
// This file is auto-generated, don't edit it
|
||||||
|
import Util, * as $Util from '@alicloud/tea-util';
|
||||||
|
import dingtalkworkflow_1_0, * as $dingtalkworkflow_1_0 from '@alicloud/dingtalk/workflow_1_0';
|
||||||
|
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
|
||||||
|
import { logger } from '../winston/logger';
|
||||||
|
|
||||||
|
|
||||||
|
export default class OAClient {
|
||||||
|
/**
|
||||||
|
* 使用 Token 初始化账号Client
|
||||||
|
* @return Client
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
static createClient(): dingtalkworkflow_1_0 {
|
||||||
|
let config = new $OpenApi.Config({});
|
||||||
|
config.protocol = "https";
|
||||||
|
config.regionId = "central";
|
||||||
|
return new dingtalkworkflow_1_0(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async listProcessInstanceIdsWithOptions(args: {
|
||||||
|
processCode: string;
|
||||||
|
startTime: number;
|
||||||
|
endTime?: number;
|
||||||
|
nextToken?: number;
|
||||||
|
maxResults: number;
|
||||||
|
userIds?: string[]
|
||||||
|
statuses?: string[]
|
||||||
|
dingtalkAccessToken: string;
|
||||||
|
}) {
|
||||||
|
let client = OAClient.createClient();
|
||||||
|
let listProcessInstanceIdsHeaders = new $dingtalkworkflow_1_0.ListProcessInstanceIdsHeaders({});
|
||||||
|
listProcessInstanceIdsHeaders.xAcsDingtalkAccessToken = args.dingtalkAccessToken;
|
||||||
|
let listProcessInstanceIdsRequest = new $dingtalkworkflow_1_0.ListProcessInstanceIdsRequest({
|
||||||
|
processCode: args.processCode,
|
||||||
|
startTime: args.startTime,
|
||||||
|
endTime: args.endTime,
|
||||||
|
nextToken: args.nextToken,
|
||||||
|
maxResults: args.maxResults ?? 20,
|
||||||
|
userIds: args.userIds ? args.userIds : [],
|
||||||
|
statuses: args.statuses ? args.statuses : [],
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await client.listProcessInstanceIdsWithOptions(listProcessInstanceIdsRequest, listProcessInstanceIdsHeaders, new $Util.RuntimeOptions({}));
|
||||||
|
console.log("listProcessInstanceIdsWithOptions 结果", res)
|
||||||
|
return res;
|
||||||
|
} catch (err: any) {
|
||||||
|
logger.error("listProcessInstanceIdsWithOptions", err)
|
||||||
|
console.error("listProcessInstanceIdsWithOptions")
|
||||||
|
console.error(err)
|
||||||
|
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||||
|
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getProcessInstanceWithOptions(args: {
|
||||||
|
processInstanceId: string;
|
||||||
|
dingtalkAccessToken: string;
|
||||||
|
}) {
|
||||||
|
let client = OAClient.createClient();
|
||||||
|
let getProcessInstanceHeaders = new $dingtalkworkflow_1_0.GetProcessInstanceHeaders({});
|
||||||
|
getProcessInstanceHeaders.xAcsDingtalkAccessToken = args.dingtalkAccessToken;
|
||||||
|
let getProcessInstanceRequest = new $dingtalkworkflow_1_0.GetProcessInstanceRequest({
|
||||||
|
processInstanceId: args.processInstanceId,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new $Util.RuntimeOptions({}));
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
processInstanceId: args.processInstanceId,
|
||||||
|
};
|
||||||
|
} catch (err: any) {
|
||||||
|
logger.error("getProcessInstanceWithOptions", err)
|
||||||
|
console.error("getProcessInstanceWithOptions")
|
||||||
|
console.error(err)
|
||||||
|
if (!Util.empty(err.code) && !Util.empty(err.message)) {
|
||||||
|
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/api/winston/logger.ts
Normal file
29
src/api/winston/logger.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import winston from 'winston';
|
||||||
|
|
||||||
|
export const logger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: winston.format.json(),
|
||||||
|
defaultMeta: { service: 'user-service' },
|
||||||
|
transports: [
|
||||||
|
//
|
||||||
|
// - Write all logs with importance level of `error` or higher to `error.log`
|
||||||
|
// (i.e., error, fatal, but not other levels)
|
||||||
|
//
|
||||||
|
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||||
|
//
|
||||||
|
// - Write all logs with importance level of `info` or higher to `combined.log`
|
||||||
|
// (i.e., fatal, error, warn, and info, but not trace)
|
||||||
|
//
|
||||||
|
new winston.transports.File({ filename: 'combined.log' }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// If we're not in production then log to the `console` with the format:
|
||||||
|
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
|
||||||
|
//
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
logger.add(new winston.transports.Console({
|
||||||
|
format: winston.format.simple(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
26
src/app/globals.css
Normal file
26
src/app/globals.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
34
src/app/layout.tsx
Normal file
34
src/app/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
// import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
// const geistSans = Geist({
|
||||||
|
// variable: "--font-geist-sans",
|
||||||
|
// subsets: ["latin"],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const geistMono = Geist_Mono({
|
||||||
|
// variable: "--font-geist-mono",
|
||||||
|
// subsets: ["latin"],
|
||||||
|
// });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Create Next App",
|
||||||
|
description: "Generated by create next app",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body
|
||||||
|
className={` antialiased`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
src/app/page.tsx
Normal file
103
src/app/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||||
|
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||||
|
<Image
|
||||||
|
className="dark:invert"
|
||||||
|
src="/next.svg"
|
||||||
|
alt="Next.js logo"
|
||||||
|
width={180}
|
||||||
|
height={38}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||||
|
<li className="mb-2 tracking-[-.01em]">
|
||||||
|
Get started by editing{" "}
|
||||||
|
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||||
|
src/app/page.tsx
|
||||||
|
</code>
|
||||||
|
.
|
||||||
|
</li>
|
||||||
|
<li className="tracking-[-.01em]">
|
||||||
|
Save and see your changes instantly.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||||
|
<a
|
||||||
|
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||||
|
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className="dark:invert"
|
||||||
|
src="/vercel.svg"
|
||||||
|
alt="Vercel logomark"
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
Deploy now
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||||
|
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Read our docs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||||
|
<a
|
||||||
|
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||||
|
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
aria-hidden
|
||||||
|
src="/file.svg"
|
||||||
|
alt="File icon"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
Learn
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||||
|
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
aria-hidden
|
||||||
|
src="/window.svg"
|
||||||
|
alt="Window icon"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
Examples
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||||
|
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
aria-hidden
|
||||||
|
src="/globe.svg"
|
||||||
|
alt="Globe icon"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
Go to nextjs.org →
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
src/db/customer_management_info.sql
Normal file
33
src/db/customer_management_info.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : 本地数据库
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80042 (8.0.42)
|
||||||
|
Source Host : localhost:3306
|
||||||
|
Source Schema : procurement_db
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80042 (8.0.42)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 20/10/2025 14:15:36
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for customer_management_info
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `customer_management_info`;
|
||||||
|
CREATE TABLE `customer_management_info` (
|
||||||
|
`data_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据id',
|
||||||
|
`customer_unit_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户单位名称',
|
||||||
|
`customer_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户分类',
|
||||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`data_id`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客户管理信息表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
86
src/db/dingding_oa_invoice_application.sql
Normal file
86
src/db/dingding_oa_invoice_application.sql
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : 本地数据库
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80042 (8.0.42)
|
||||||
|
Source Host : localhost:3306
|
||||||
|
Source Schema : procurement_db
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80042 (8.0.42)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 20/10/2025 14:15:43
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for dingding_oa_invoice_application
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `dingding_oa_invoice_application`;
|
||||||
|
CREATE TABLE `dingding_oa_invoice_application` (
|
||||||
|
`invoiceJobNumber` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`dataId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '数据id',
|
||||||
|
`applicationDate` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请日期',
|
||||||
|
`applicant` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请人',
|
||||||
|
`applicationDepartment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请部门',
|
||||||
|
`agreementType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '项目类型',
|
||||||
|
`xiang_mu_zu_bie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '项目组别',
|
||||||
|
`yuJiBaoXiaoDiSanFangChengBenYuan` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '预计报销第三方成本(不含税,元)',
|
||||||
|
`businessStartTime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '业务开始时间',
|
||||||
|
`businessEndTime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '业务结束时间',
|
||||||
|
`contractNumber` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '销售合同编号',
|
||||||
|
`contractName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '销售合同名称',
|
||||||
|
`uploadedContractAttachment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '上传合同及相关附件',
|
||||||
|
`uploadedSealedCostListDa` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '上传盖章费用清单(大)',
|
||||||
|
-- `uploadedSealedCostListDu` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '上传盖章费用清单(独)',
|
||||||
|
`projectNumber` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '项目编号',
|
||||||
|
`is_tax_inclusive` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否含税',
|
||||||
|
`invoice_tax_rate` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '开票税率',
|
||||||
|
`invoice_tax_category` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票税目',
|
||||||
|
`is_multi_project_merge` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否多项目合并',
|
||||||
|
`customerCompanyName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '客户单位名称',
|
||||||
|
`customerCompanyNameOther` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '客户单位名称(其他)',
|
||||||
|
`tong_yi_she_hui_xin_yong_dai_ma` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '统一社会信用代码',
|
||||||
|
`bankName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '开户行名称',
|
||||||
|
`bankAccount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '银行账号',
|
||||||
|
`business_content_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '业务内容(独)',
|
||||||
|
`invoice_amount_yuan_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '开票金额(元)独',
|
||||||
|
`invoice_amount_yuan_other` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '开票金额(元)其他',
|
||||||
|
`bu_han_shui_jin_e_yuan_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)独',
|
||||||
|
-- `bu_han_shui_jin_e_yuan_du_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)独(大写)',
|
||||||
|
`han_shui_jin_e_yuan_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税金额(元)独',
|
||||||
|
-- `han_shui_jin_e_yuan_du_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税金额(元)独(大写)',
|
||||||
|
`bu_han_shui_jin_e_yuan_other` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '不含税金额(元)其他',
|
||||||
|
-- `bu_han_shui_jin_e_yuan_other_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '不含税金额(元)其他(大写)',
|
||||||
|
`han_shui_jin_e_yuan_other` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '含税金额(元)其他',
|
||||||
|
-- `han_shui_jin_e_yuan_other_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '含税金额(元)其他(大写)',
|
||||||
|
`bu_han_shui_jin_e_yuan_duo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)多',
|
||||||
|
-- `bu_han_shui_jin_e_yuan_duo_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)多(大写)',
|
||||||
|
`han_shui_jin_e_yuan_duo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税金额(元)多',
|
||||||
|
-- `han_shui_jin_e_yuan_duo_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税金额(元)多(大写)',
|
||||||
|
`bu_han_shui_jin_e_yuan_da` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)大',
|
||||||
|
`han_shui_zong_jin_e_yuan_da` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税总金额(元)大',
|
||||||
|
`bu_han_shui_jin_e_yuan_da_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)(大+独)',
|
||||||
|
-- `bu_han_shui_jin_e_yuan_da_du_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '不含税金额(元)(大+独)(大写)',
|
||||||
|
`han_shui_zong_jin_e_yuan_da_du` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税总金额(元)(大+独)',
|
||||||
|
-- `han_shui_zong_jin_e_yuan_da_du_da_xie` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '含税总金额(元)(大+独)(大写)',
|
||||||
|
`approvalNumber` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批编号',
|
||||||
|
`creationTime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`creator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
|
||||||
|
`currentResponsiblePerson` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '当前负责人',
|
||||||
|
`approvalResult` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批结果',
|
||||||
|
`approvalStatus` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批状态',
|
||||||
|
`updateTime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新时间',
|
||||||
|
`creatorDepartment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人部门',
|
||||||
|
`approvalFormTitle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批单标题',
|
||||||
|
`historicalApprovers` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '历史审批人',
|
||||||
|
`timeConsumed` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '耗时(时:分:秒)',
|
||||||
|
`approvalRecord` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '审批记录',
|
||||||
|
PRIMARY KEY (`invoiceJobNumber`, `dataId`) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
137
src/db/fix_missing_columns.sql
Normal file
137
src/db/fix_missing_columns.sql
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
-- 修复缺失的列
|
||||||
|
ALTER TABLE `dingding_oa_invoice_application`
|
||||||
|
ADD COLUMN IF NOT EXISTS `invoiceType` varchar(255) NULL COMMENT '发票类型',
|
||||||
|
ADD COLUMN IF NOT EXISTS `invoiceCode` tinyint NULL COMMENT '发票代码',
|
||||||
|
ADD COLUMN IF NOT EXISTS `kai_piao_shi_you` varchar(255) NULL COMMENT '开票事由',
|
||||||
|
ADD COLUMN IF NOT EXISTS `fa_piao_bei_zhu_lan_tian_xie_nei_rong` text NULL COMMENT '发票备注栏填写内容';
|
||||||
|
|
||||||
|
-- 补充:为明细表新增“预计报销第三方成本(不含税,元)”
|
||||||
|
ALTER TABLE `invoice_amount_details`
|
||||||
|
ADD COLUMN IF NOT EXISTS `yu_ji_bao_xiao_di_san_fang_cheng_ben_yuan` varchar(255) NULL COMMENT '预计报销第三方成本(不含税,元)';
|
||||||
|
|
||||||
|
-- 重新创建视图以解决权限问题
|
||||||
|
DROP VIEW IF EXISTS `invoice_application_match_view`;
|
||||||
|
CREATE VIEW `invoice_application_match_view` AS
|
||||||
|
SELECT
|
||||||
|
`da`.`dataId` AS `dataId`,
|
||||||
|
`da`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`da`.`agreementType` AS `agreementType`,
|
||||||
|
`da`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`da`.`contractNumber` AS `salesContractNumber`,
|
||||||
|
`da`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`da`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`da`.`projectNumber` AS `projectNumber`,
|
||||||
|
`da`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`da`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`id`.`piao_hao` AS `piao_hao`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`applicant` AS `applicant`,
|
||||||
|
`da`.`business_content_du` AS `business_content_du`,
|
||||||
|
`da`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`da`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`da`.`invoiceType` AS `invoiceType`,
|
||||||
|
`da`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`da`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`da`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`id`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`id`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`id`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`id`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`id`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`cmi`.`customer_type` AS `unit_nature`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
`dingding_oa_invoice_application` `da`
|
||||||
|
LEFT JOIN `invoice_data` `id` ON (
|
||||||
|
`da`.`dataId` = `id`.`dataid`
|
||||||
|
)
|
||||||
|
LEFT JOIN `customer_management_info` `cmi` ON (
|
||||||
|
`da`.`customerCompanyName` = `cmi`.`customer_unit_name`
|
||||||
|
))
|
||||||
|
SQL SECURITY INVOKER;
|
||||||
|
|
||||||
|
-- 同样修复内层视图
|
||||||
|
DROP VIEW IF EXISTS `invoice_application_match_inner_view`;
|
||||||
|
CREATE VIEW `invoice_application_match_inner_view` AS
|
||||||
|
SELECT
|
||||||
|
`da`.`dataId` AS `dataId`,
|
||||||
|
`da`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`da`.`agreementType` AS `agreementType`,
|
||||||
|
`da`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`da`.`contractNumber` AS `salesContractNumber`,
|
||||||
|
`da`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`da`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`da`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`da`.`projectNumber` AS `projectNumber`,
|
||||||
|
`da`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`da`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`id`.`piao_hao` AS `piao_hao`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`applicant` AS `applicant`,
|
||||||
|
`da`.`business_content_du` AS `business_content_du`,
|
||||||
|
`da`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`da`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`da`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`da`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`da`.`invoiceType` AS `invoiceType`,
|
||||||
|
`da`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`id`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`id`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`id`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`id`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`id`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`cmi`.`customer_type` AS `unit_nature`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`dingding_oa_invoice_application` `da`
|
||||||
|
LEFT JOIN `invoice_data` `id` ON((`da`.`dataId` = `id`.`dataid`))
|
||||||
|
)
|
||||||
|
LEFT JOIN `customer_management_info` `cmi` ON(
|
||||||
|
(
|
||||||
|
`da`.`customerCompanyName` = `cmi`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SQL SECURITY INVOKER;
|
||||||
|
|
||||||
|
-- 费用报销表新增字段(适配新版费用报销导入模板)
|
||||||
|
ALTER TABLE `expense_reimbursement`
|
||||||
|
ADD COLUMN IF NOT EXISTS `xuhao` varchar(255) NULL COMMENT '序号',
|
||||||
|
ADD COLUMN IF NOT EXISTS `date` varchar(255) NULL COMMENT '日期',
|
||||||
|
ADD COLUMN IF NOT EXISTS `voucher_number` varchar(255) NULL COMMENT '凭证号',
|
||||||
|
ADD COLUMN IF NOT EXISTS `entry_number` varchar(255) NULL COMMENT '分录号',
|
||||||
|
ADD COLUMN IF NOT EXISTS `summary` varchar(255) NULL COMMENT '摘要',
|
||||||
|
ADD COLUMN IF NOT EXISTS `account_code` varchar(255) NULL COMMENT '科目编码',
|
||||||
|
ADD COLUMN IF NOT EXISTS `account_name` varchar(255) NULL COMMENT '科目名称',
|
||||||
|
ADD COLUMN IF NOT EXISTS `auxiliary_items` varchar(1000) NULL COMMENT '辅助项',
|
||||||
|
ADD COLUMN IF NOT EXISTS `currency` varchar(255) NULL COMMENT '币种',
|
||||||
|
ADD COLUMN IF NOT EXISTS `debit_original_currency` varchar(255) NULL COMMENT '借方原币',
|
||||||
|
ADD COLUMN IF NOT EXISTS `debit_local_currency` varchar(255) NULL COMMENT '借方本币',
|
||||||
|
ADD COLUMN IF NOT EXISTS `credit_original_currency` varchar(255) NULL COMMENT '贷方原币',
|
||||||
|
ADD COLUMN IF NOT EXISTS `credit_local_currency` varchar(255) NULL COMMENT '贷方本币',
|
||||||
|
ADD COLUMN IF NOT EXISTS `shared_voucher_number` varchar(255) NULL COMMENT '共享凭证号',
|
||||||
|
ADD COLUMN IF NOT EXISTS `expense_budget_item` varchar(255) NULL COMMENT '费用预算项目',
|
||||||
|
ADD COLUMN IF NOT EXISTS `cost_center` varchar(255) NULL COMMENT '成本中心',
|
||||||
|
ADD COLUMN IF NOT EXISTS `standard_name` varchar(255) NULL COMMENT '标准名称',
|
||||||
|
ADD COLUMN IF NOT EXISTS `expense_type_level1` varchar(255) NULL COMMENT '一级费用类型',
|
||||||
|
ADD COLUMN IF NOT EXISTS `expense_type_level2` varchar(255) NULL COMMENT '二级费用类型',
|
||||||
|
ADD COLUMN IF NOT EXISTS `expense_type_level3` varchar(255) NULL COMMENT '三级费用类型';
|
||||||
159
src/db/invoice_application_match_view.sql
Normal file
159
src/db/invoice_application_match_view.sql
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
SELECT
|
||||||
|
`t`.`dataId` AS `dataId`,
|
||||||
|
`t`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`t`.`agreementType` AS `agreementType`,
|
||||||
|
`t`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`t`.`salesContractNumber` AS `salesContractNumber`,
|
||||||
|
`t`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`t`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`t`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`t`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`t`.`projectNumber` AS `projectNumber`,
|
||||||
|
`t`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`t`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`t`.`piao_hao` AS `piao_hao`,
|
||||||
|
`t`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`t`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`t`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`t`.`han_shui_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`t`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`t`.`han_shui_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`t`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`t`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`t`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`t`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`t`.`applicant` AS `applicant`,
|
||||||
|
`t`.`business_content_du` AS `business_content_du`,
|
||||||
|
`t`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`t`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`t`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`t`.`invoiceType` AS `invoiceType`,
|
||||||
|
`t`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`t`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`t`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`t`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`t`.`bankName` AS `bankName`,
|
||||||
|
`t`.`bankAccount` AS `bankAccount`,
|
||||||
|
`t`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`t`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`t`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`t`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`t`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`t`.`unit_nature` AS `unit_nature`,
|
||||||
|
`t`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`da`.`dataId` AS `dataId`,
|
||||||
|
`da`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`da`.`agreementType` AS `agreementType`,
|
||||||
|
`da`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`da`.`contractNumber` AS `salesContractNumber`,
|
||||||
|
`da`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`da`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`da`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`da`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`da`.`projectNumber` AS `projectNumber`,
|
||||||
|
`da`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`da`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`id`.`piao_hao` AS `piao_hao`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`applicant` AS `applicant`,
|
||||||
|
`da`.`business_content_du` AS `business_content_du`,
|
||||||
|
`da`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`da`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`da`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`da`.`invoiceType` AS `invoiceType`,
|
||||||
|
`da`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`da`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`da`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`da`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`da`.`bankName` AS `bankName`,
|
||||||
|
`da`.`bankAccount` AS `bankAccount`,
|
||||||
|
`id`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`id`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`id`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`id`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`id`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`cmi`.`customer_type` AS `unit_nature`,
|
||||||
|
`da`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY CASE
|
||||||
|
WHEN `da`.`is_single_contract_multi_customer` = '是' THEN CONCAT(`da`.`dataId`, '#', `da`.`invoiceJobNumber`)
|
||||||
|
ELSE IFNULL(
|
||||||
|
`id`.`piao_hao`,
|
||||||
|
CONCAT(`da`.`dataId`, '#', `da`.`invoiceJobNumber`)
|
||||||
|
)
|
||||||
|
END
|
||||||
|
ORDER BY
|
||||||
|
`da`.`invoiceJobNumber` DESC
|
||||||
|
) AS `rn`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`dingding_oa_invoice_application` `da`
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`dataid`,
|
||||||
|
`customer_company_name`,
|
||||||
|
`piao_hao`,
|
||||||
|
MAX(`invoice_type_enum`) AS `invoice_type_enum`,
|
||||||
|
MAX(`kai_piao_ri_qi`) AS `kai_piao_ri_qi`,
|
||||||
|
MAX(`hui_kuan_ri_qi`) AS `hui_kuan_ri_qi`,
|
||||||
|
MAX(`hui_kuan_jin_e`) AS `hui_kuan_jin_e`,
|
||||||
|
MAX(`zhang_qi`) AS `zhang_qi`
|
||||||
|
FROM
|
||||||
|
`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`dataid`,
|
||||||
|
`customer_company_name`,
|
||||||
|
`piao_hao`
|
||||||
|
) `id` ON(
|
||||||
|
(
|
||||||
|
(`da`.`dataId` = `id`.`dataid`)
|
||||||
|
AND (
|
||||||
|
`da`.`customerCompanyName` = `id`.`customer_company_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`cmi1`.`customer_unit_name`,
|
||||||
|
MAX(`cmi1`.`customer_type`) AS `customer_type`
|
||||||
|
FROM
|
||||||
|
`customer_management_info` `cmi1`
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT
|
||||||
|
`customer_unit_name`,
|
||||||
|
MAX(`updated_at`) AS `updated_at`
|
||||||
|
FROM
|
||||||
|
`customer_management_info`
|
||||||
|
GROUP BY
|
||||||
|
`customer_unit_name`
|
||||||
|
) `cmi_latest` ON(
|
||||||
|
(
|
||||||
|
(`cmi1`.`customer_unit_name` = `cmi_latest`.`customer_unit_name`)
|
||||||
|
AND (`cmi1`.`updated_at` = `cmi_latest`.`updated_at`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
`cmi1`.`customer_unit_name`
|
||||||
|
) `cmi` ON(
|
||||||
|
(
|
||||||
|
`da`.`customerCompanyName` = `cmi`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) `t`
|
||||||
|
WHERE
|
||||||
|
`t`.`rn` = 1
|
||||||
37
src/db/invoice_data.sql
Normal file
37
src/db/invoice_data.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : 本地数据库
|
||||||
|
Source Server Type : MySQL
|
||||||
|
Source Server Version : 80042 (8.0.42)
|
||||||
|
Source Host : localhost:3306
|
||||||
|
Source Schema : procurement_db
|
||||||
|
|
||||||
|
Target Server Type : MySQL
|
||||||
|
Target Server Version : 80042 (8.0.42)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 20/10/2025 14:15:27
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for invoice_data
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `invoice_data`;
|
||||||
|
CREATE TABLE `invoice_data` (
|
||||||
|
`dataid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
`piao_hao` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '票号',
|
||||||
|
`kai_piao_ri_qi` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '开票日期',
|
||||||
|
`hui_kuan_ri_qi` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '回款日期',
|
||||||
|
`hui_kuan_jin_e` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '回款金额',
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`invoice_type_enum` int NULL DEFAULT 0 COMMENT '0 普通票\r\n1 作废-当月\r\n2 作废红冲\r\n',
|
||||||
|
`zhang_qi` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '账期,根据回款日期减去开票日期而得',
|
||||||
|
PRIMARY KEY (`dataid`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '开票数据表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
13
src/lib/prisma.ts
Normal file
13
src/lib/prisma.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
// 简化配置,直接实例化 PrismaClient
|
||||||
|
const globalForPrisma = globalThis as unknown as { prismaClient?: PrismaClient }
|
||||||
|
export const prismaClient = new PrismaClient({
|
||||||
|
datasources: {
|
||||||
|
db: {
|
||||||
|
url: process.env.DATABASE_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prismaClient = prismaClient
|
||||||
|
export default prismaClient
|
||||||
215
src/lib/util.ts
Normal file
215
src/lib/util.ts
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { AGREEMENT_TYPES, AGREEMENT_TYPE_CODE, AGREEMENT_TYPE_NAME, AGREEMENT_CODE_TO_NAME, AGREEMENT_CODE_TO_TYPE, XIANGMU_ZUBIE_MAP } from '@/CONSTANTS';
|
||||||
|
import { InvoiceDataItem, ProcessedInvoiceDataItemFromFunction } from '@/pages/api/type';
|
||||||
|
import { getBuHanShuiAmount, getHanShuiAmount, getAllAmount } from '@/pages/api/approval/util';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import DownLoadClient from '@/api/dingding/download';
|
||||||
|
import { logger } from '@/api/winston/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理发票数据,添加客户公司名称、账期和协议类型名称
|
||||||
|
* @param item 原始发票数据项
|
||||||
|
* @returns 处理后的发票数据项
|
||||||
|
*/
|
||||||
|
export function processInvoiceDataItem(item: any): any {
|
||||||
|
// 添加 customerCompanyName 字段
|
||||||
|
let customerCompanyName = item.customerCompanyName || "";
|
||||||
|
|
||||||
|
// 根据协议类型选择不含税金额字段
|
||||||
|
let bu_han_shui_jin_e: string = getBuHanShuiAmount(item);
|
||||||
|
|
||||||
|
// 根据协议类型选择含税金额字段
|
||||||
|
let han_shui_zong_jin_e = getHanShuiAmount(item);
|
||||||
|
|
||||||
|
// 计算税额(含税金额 - 不含税金额)
|
||||||
|
const shui_e = getAllAmount(bu_han_shui_jin_e, han_shui_zong_jin_e);
|
||||||
|
// 根据新的协议类型显示逻辑
|
||||||
|
let agreementTypeName = '';
|
||||||
|
if (item.agreementType === '2' || item.agreementType === '4') {
|
||||||
|
agreementTypeName = '独立协议';
|
||||||
|
} else if (item.agreementType === '1' || item.agreementType === '3') {
|
||||||
|
agreementTypeName = '大协议';
|
||||||
|
} else {
|
||||||
|
agreementTypeName = AGREEMENT_CODE_TO_NAME[item.agreementType as keyof typeof AGREEMENT_TYPE_NAME] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务内容统一取开票事由
|
||||||
|
const businessContent = item.kai_piao_shi_you || '';
|
||||||
|
|
||||||
|
// 专业组映射逻辑 - 基于项目编号的字母前缀
|
||||||
|
const getProjectGroupByProjectId = (projectId: string | null | undefined): string => {
|
||||||
|
if (!projectId) {
|
||||||
|
return '客户服务室';
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = projectId.match(/[A-Za-z]+/);
|
||||||
|
const prefix = match ? match[0] : '';
|
||||||
|
|
||||||
|
const groupMapping: { [key: string]: string } = {
|
||||||
|
'YY': '运营室',
|
||||||
|
'ZP': '人员招聘项目组',
|
||||||
|
'ZW': '总务服务项目组',
|
||||||
|
'XC': '薪酬核算项目组',
|
||||||
|
'TX': '退休衔接服务项目组',
|
||||||
|
'TC': '统筹支持项目组',
|
||||||
|
'RZL': '人员入转离项目组',
|
||||||
|
'PJ': '人才评鉴项目组',
|
||||||
|
'HW': '会务服务项目组',
|
||||||
|
'GJ': '国际化服务项目组',
|
||||||
|
'GZ': '管理咨询项目组',
|
||||||
|
'KF': '客户服务室',
|
||||||
|
'FL': '福利业务项目组',
|
||||||
|
'FW': '法务合规中心',
|
||||||
|
'SJ': '数字化与交付项目组',
|
||||||
|
'KJ': '科技培训项目组',
|
||||||
|
'JG': '经营管理项目组',
|
||||||
|
'JN': '技能培训项目组',
|
||||||
|
'DJ': '党建培训项目组',
|
||||||
|
'JH': '计划核定项目组',
|
||||||
|
'LGB': '老干部服务项目组'
|
||||||
|
};
|
||||||
|
|
||||||
|
return groupMapping[prefix] || '客户服务室';
|
||||||
|
};
|
||||||
|
|
||||||
|
const zhuan_ye_zu = getProjectGroupByProjectId(item.projectNumber);
|
||||||
|
|
||||||
|
// 账期计算逻辑
|
||||||
|
let zhang_qi = '';
|
||||||
|
|
||||||
|
const kai_piao_ri_qi = item.kai_piao_ri_qi;
|
||||||
|
const hui_kuan_ri_qi = item.hui_kuan_ri_qi;
|
||||||
|
|
||||||
|
// 优先检查回款日期:如果有回款日期,则没有账期
|
||||||
|
if (hui_kuan_ri_qi) {
|
||||||
|
zhang_qi = '';
|
||||||
|
} else if (kai_piao_ri_qi) {
|
||||||
|
// 只有开票日期:账期 = 当前日期 - 开票日期(单位:天)
|
||||||
|
const kp = dayjs(kai_piao_ri_qi);
|
||||||
|
const now = dayjs();
|
||||||
|
if (kp.isValid()) {
|
||||||
|
const dayDiff = now.diff(kp, 'day');
|
||||||
|
zhang_qi = dayDiff.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回包含新字段的对象
|
||||||
|
// const zhuan_ye_zu = XIANGMU_ZUBIE_MAP[item.applicationDepartment || ''] || '项目组别匹配失败';
|
||||||
|
// 发票类型:从开票申请中获取
|
||||||
|
const fa_piao_lei_xing = item.invoiceType || '';
|
||||||
|
return {
|
||||||
|
invoiceJobNumber: item.invoiceJobNumber || '',
|
||||||
|
dataId: item.dataId,
|
||||||
|
approvalNumber: item.approvalNumber || null,
|
||||||
|
// 去掉 unit_nature(集团内外)
|
||||||
|
projectNumber: item.projectNumber,
|
||||||
|
agreementType: item.agreementType,
|
||||||
|
agreementTypeName: agreementTypeName,
|
||||||
|
invoiceType: item.invoiceType || null,
|
||||||
|
customerCompanyName: customerCompanyName,
|
||||||
|
piao_hao: item.piao_hao,
|
||||||
|
bu_han_shui_jin_e: bu_han_shui_jin_e,
|
||||||
|
shui_e: shui_e,
|
||||||
|
han_shui_zong_jin_e: han_shui_zong_jin_e,
|
||||||
|
applicant: item.applicant,
|
||||||
|
zhuan_ye_zu,
|
||||||
|
kai_piao_ri_qi: item.kai_piao_ri_qi,
|
||||||
|
hui_kuan_ri_qi: item.hui_kuan_ri_qi,
|
||||||
|
hui_kuan_jin_e: item.hui_kuan_jin_e,
|
||||||
|
business_content: businessContent,
|
||||||
|
zhang_qi: zhang_qi, // 使用计算后的账期值
|
||||||
|
invoice_type_enum: item.invoice_type_enum,
|
||||||
|
// 透传新增字段
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: item.tong_yi_she_hui_xin_yong_dai_ma || null,
|
||||||
|
bankName: item.bankName || null,
|
||||||
|
bankAccount: item.bankAccount || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件并保存到指定路径
|
||||||
|
* @param downloadUri 文件下载URL
|
||||||
|
* @param attachmentFileName 保存的文件名
|
||||||
|
* @param attachmentFilePath 保存的文件路径
|
||||||
|
* @returns Promise<boolean> 下载是否成功
|
||||||
|
*/
|
||||||
|
export async function downloadAndSaveFile(
|
||||||
|
downloadUri: string,
|
||||||
|
attachmentFilePath: string,
|
||||||
|
attachmentFileName: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(downloadUri);
|
||||||
|
if (response.ok) {
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
|
const fileFullPath = path.join(attachmentFilePath, attachmentFileName);
|
||||||
|
fs.writeFileSync(fileFullPath, buffer);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.error(`Download failed: ${response.status} ${response.statusText}`);
|
||||||
|
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error downloading file from ${downloadUri}:`, error);
|
||||||
|
throw new Error(`文件下载失败: ${downloadUri} - ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载附件功能 - 处理JSON格式的附件数据并下载保存
|
||||||
|
* @param attachmentJsonString 附件数据的JSON字符串
|
||||||
|
* @param folderPath 保存文件夹路径
|
||||||
|
* @param subFolderName 子文件夹名称
|
||||||
|
* @param dataId 数据ID
|
||||||
|
* @param accessToken 访问令牌
|
||||||
|
* @param fileNamePrefix 文件名前缀
|
||||||
|
* @returns Promise<void>
|
||||||
|
*/
|
||||||
|
export async function downloadAttachments(
|
||||||
|
attachmentJsonString: string,
|
||||||
|
folderPath: string,
|
||||||
|
subFolderName: string,
|
||||||
|
dataId: string,
|
||||||
|
accessToken: string,
|
||||||
|
fileNamePrefix: string = ''
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log("attachmentJsonString",attachmentJsonString)
|
||||||
|
const attachmentData = JSON.parse(attachmentJsonString);
|
||||||
|
// 检查是否为数组
|
||||||
|
const attachments = Array.isArray(attachmentData) ? attachmentData : [attachmentData];
|
||||||
|
|
||||||
|
// 创建子文件夹
|
||||||
|
const subFolderPath = path.join(folderPath, subFolderName);
|
||||||
|
if (!fs.existsSync(subFolderPath)) {
|
||||||
|
fs.mkdirSync(subFolderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
// 遍历所有附件
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (attachment && attachment.fileId) {
|
||||||
|
// 调用下载接口获取文件URL
|
||||||
|
const fileUrl = await DownLoadClient.getFileURL({
|
||||||
|
processInstanceId: dataId,
|
||||||
|
fileId: attachment.fileId,
|
||||||
|
xAcsDingtalkAccessToken: accessToken
|
||||||
|
});
|
||||||
|
const downloadUri = get(fileUrl, 'body.result.downloadUri', '');
|
||||||
|
if (downloadUri) {
|
||||||
|
const fileName = `${fileNamePrefix}${attachment.fileName || attachment.fileId || 'file'}`;
|
||||||
|
await downloadAndSaveFile(downloadUri, subFolderPath, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
console.log(`${subFolderName} for ${dataId} processing failed:`, error instanceof Error ? error.message : String(error));
|
||||||
|
logger.error(`${subFolderName} for ${dataId} processing failed:`, error instanceof Error ? error.message : String(error));
|
||||||
|
throw new Error(`${subFolderName} for ${dataId} processing failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
73
src/pages/api/actual_payment/import.ts
Normal file
73
src/pages/api/actual_payment/import.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { InvoiceApplicationItem } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
const ExcelToMySQLFieldMapping = {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'支付日期': 'payment_date',
|
||||||
|
'年份': 'payment_year',
|
||||||
|
'凭证号': 'pingzhenghao',
|
||||||
|
'行项目文本': 'hangxiangmu_wenben',
|
||||||
|
'业务线': 'business_line',
|
||||||
|
'期末余额(元)(大写)': 'qimo_yueyuan_daxie',
|
||||||
|
'支付方式': 'payment_method',
|
||||||
|
'供应商名称': 'gongyingshang_mingcheng',
|
||||||
|
'创建时间': 'created_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'updated_time'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: InvoiceApplicationItem[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
let actualPaymentDataList = [];
|
||||||
|
// TODO 可以查询一次搞重新修改 看业务需求
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const actualPaymentData: any = {
|
||||||
|
data_id: rawItem['数据id'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
actualPaymentData[dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
actualPaymentDataList.push(actualPaymentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据到actual_payment表
|
||||||
|
await prismaClient.actual_payment.createMany({
|
||||||
|
data: actualPaymentDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: '实际支付数据导入成功'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
343
src/pages/api/approval/exportInvoiceData.ts
Normal file
343
src/pages/api/approval/exportInvoiceData.ts
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import XLSXStyle from 'xlsx-style';
|
||||||
|
import { getHanShuiAmount, getBuHanShuiAmount, getAllAmount } from '@/pages/api/approval/util'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { AGREEMENT_CODE_TO_NAME, AGREEMENT_TYPE_CODE, AGREEMENT_TYPES, XIANGMU_ZUBIE_MAP } from '@/CONSTANTS'
|
||||||
|
|
||||||
|
// 定义列宽配置
|
||||||
|
// 列宽:移除“集团内外”
|
||||||
|
const COLUMN_WIDTHS = {
|
||||||
|
"数据ID": 20,
|
||||||
|
"项目编号": 15,
|
||||||
|
"单位": 25,
|
||||||
|
"票号": 15,
|
||||||
|
"不含税金额": 15,
|
||||||
|
"税额": 15,
|
||||||
|
"含税金额": 15,
|
||||||
|
"发票类型": 20,
|
||||||
|
"申请人": 15,
|
||||||
|
"专业组": 15,
|
||||||
|
"开票日期": 15,
|
||||||
|
"账期": 15,
|
||||||
|
"回款日期": 15,
|
||||||
|
"回款金额": 15,
|
||||||
|
"统一社会信用代码": 25,
|
||||||
|
"开户行名称": 25,
|
||||||
|
"银行账号": 25,
|
||||||
|
"业务内容": 30
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义表头样式
|
||||||
|
const HEADER_STYLE = {
|
||||||
|
fill: {
|
||||||
|
fgColor: { rgb: "bce672" } // 蓝色背景
|
||||||
|
},
|
||||||
|
font: {
|
||||||
|
color: { rgb: "000000" }, // 白色字体
|
||||||
|
bold: true
|
||||||
|
},
|
||||||
|
alignment: {
|
||||||
|
horizontal: "center",
|
||||||
|
vertical: "center"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义数据单元格样式
|
||||||
|
const CELL_STYLE = {
|
||||||
|
alignment: {
|
||||||
|
horizontal: "left",
|
||||||
|
vertical: "center"
|
||||||
|
},
|
||||||
|
numFmt: "@"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
res.setTimeout(300000);
|
||||||
|
// Only allow POST requests
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ message: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ids } = req.body
|
||||||
|
console.log('Received IDs:', ids);
|
||||||
|
|
||||||
|
let invoiceData: Array<{ [k: string]: any }>
|
||||||
|
if (Array.isArray(ids) && ids.length > 0) {
|
||||||
|
if (ids.length > 1000) {
|
||||||
|
return res.status(400).json({ message: 'Too many IDs requested. Maximum 1000 IDs allowed.' })
|
||||||
|
}
|
||||||
|
console.log('Fetching data for IDs:', ids);
|
||||||
|
invoiceData = await prismaClient.invoice_application_match_view.findMany({
|
||||||
|
where: { dataId: { in: ids } },
|
||||||
|
orderBy: { invoiceJobNumber: 'asc' },
|
||||||
|
select: {
|
||||||
|
dataId: true,
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
projectNumber: true,
|
||||||
|
customerCompanyName: true,
|
||||||
|
piao_hao: true,
|
||||||
|
kai_piao_ri_qi: true,
|
||||||
|
zhang_qi: true,
|
||||||
|
hui_kuan_ri_qi: true,
|
||||||
|
hui_kuan_jin_e: true,
|
||||||
|
applicant: true,
|
||||||
|
invoiceType: true,
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: true,
|
||||||
|
bankName: true,
|
||||||
|
bankAccount: true,
|
||||||
|
kai_piao_shi_you: true,
|
||||||
|
agreementType: true,
|
||||||
|
is_multi_project_merge: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da: true,
|
||||||
|
bu_han_shui_jin_e_yuan_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: true,
|
||||||
|
bu_han_shui_jin_e_yuan_other: true,
|
||||||
|
han_shui_jin_e_yuan_da: true,
|
||||||
|
han_shui_jin_e_yuan_du: true,
|
||||||
|
han_shui_jin_e_yuan_duo: true,
|
||||||
|
han_shui_jin_e_yuan_other: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 未传 ids 时导出全部(排除作废)
|
||||||
|
const voidRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { remark: '发票作废' },
|
||||||
|
select: { dataId: true, invoiceJobNumber: true }
|
||||||
|
});
|
||||||
|
const voidPairs = voidRecords
|
||||||
|
.filter(r => r.dataId && r.invoiceJobNumber)
|
||||||
|
.map(r => ({ dataId: r.dataId as string, invoiceJobNumber: r.invoiceJobNumber as number }));
|
||||||
|
const whereForAll: any = {};
|
||||||
|
if (voidPairs.length > 0) {
|
||||||
|
whereForAll.AND = [
|
||||||
|
{
|
||||||
|
NOT: {
|
||||||
|
OR: voidPairs.map(pair => ({
|
||||||
|
dataId: pair.dataId,
|
||||||
|
invoiceJobNumber: pair.invoiceJobNumber
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
console.log('Fetching all non-void data');
|
||||||
|
invoiceData = await prismaClient.invoice_application_match_view.findMany({
|
||||||
|
where: whereForAll,
|
||||||
|
orderBy: { invoiceJobNumber: 'asc' },
|
||||||
|
select: {
|
||||||
|
dataId: true,
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
projectNumber: true,
|
||||||
|
customerCompanyName: true,
|
||||||
|
piao_hao: true,
|
||||||
|
kai_piao_ri_qi: true,
|
||||||
|
zhang_qi: true,
|
||||||
|
hui_kuan_ri_qi: true,
|
||||||
|
hui_kuan_jin_e: true,
|
||||||
|
applicant: true,
|
||||||
|
invoiceType: true,
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: true,
|
||||||
|
bankName: true,
|
||||||
|
bankAccount: true,
|
||||||
|
kai_piao_shi_you: true,
|
||||||
|
agreementType: true,
|
||||||
|
is_multi_project_merge: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da: true,
|
||||||
|
bu_han_shui_jin_e_yuan_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: true,
|
||||||
|
bu_han_shui_jin_e_yuan_other: true,
|
||||||
|
han_shui_jin_e_yuan_da: true,
|
||||||
|
han_shui_jin_e_yuan_du: true,
|
||||||
|
han_shui_jin_e_yuan_duo: true,
|
||||||
|
han_shui_jin_e_yuan_other: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取大协议的dataId
|
||||||
|
const largeAgreementIds = invoiceData
|
||||||
|
.filter(item => item.agreementType === '1')
|
||||||
|
.map(item => item.dataId);
|
||||||
|
|
||||||
|
// 从invoice_amount_details获取大协议的业务内容
|
||||||
|
const largeAgreementDetails = await prismaClient.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: {
|
||||||
|
in: largeAgreementIds
|
||||||
|
},
|
||||||
|
agreementType: AGREEMENT_TYPE_CODE[AGREEMENT_TYPES.LARGE]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建一个映射,存储每个dataid对应的多个business_content
|
||||||
|
const businessContentMap = new Map<string, string[]>();
|
||||||
|
largeAgreementDetails.forEach((detail, idx) => {
|
||||||
|
if (detail.dataid && detail.business_content) {
|
||||||
|
if (!businessContentMap.has(detail.dataid)) {
|
||||||
|
businessContentMap.set(detail.dataid, []);
|
||||||
|
}
|
||||||
|
businessContentMap.get(detail.dataid)?.push(`${idx}. [${detail.project_name}]${detail.business_content}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Fetched data count:', invoiceData.length);
|
||||||
|
const modifiedInvoiceData = invoiceData.map(item => {
|
||||||
|
const processed = processInvoiceDataItem(item)
|
||||||
|
const buHanShuiAmount = getBuHanShuiAmount(item)
|
||||||
|
const hanShuiAmount = getHanShuiAmount(item)
|
||||||
|
const AllAmount = getAllAmount(buHanShuiAmount, hanShuiAmount);
|
||||||
|
|
||||||
|
// 业务内容字段统一取开票事由
|
||||||
|
const businessContent = item.kai_piao_shi_you || '';
|
||||||
|
|
||||||
|
// 专业组逻辑:提取项目编号的所有字母部分映射,如果没有项目编号则为客户服务室
|
||||||
|
const getProjectGroupByProjectId = (projectId: string | null | undefined): string => {
|
||||||
|
if (!projectId) {
|
||||||
|
return '客户服务室';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目编号的所有字母部分(匹配连续的字母字符,不限位数)
|
||||||
|
const letterMatch = projectId.match(/^[A-Za-z]+/);
|
||||||
|
const prefix = letterMatch ? letterMatch[0] : '';
|
||||||
|
|
||||||
|
// 根据字母前缀映射专业组
|
||||||
|
const groupMapping: { [key: string]: string } = {
|
||||||
|
'YY': '运营室',
|
||||||
|
'ZP': '人员招聘项目组',
|
||||||
|
'ZW': '总务服务项目组',
|
||||||
|
'XC': '薪酬核算项目组',
|
||||||
|
'TX': '退休衔接服务项目组',
|
||||||
|
'TC': '统筹支持项目组',
|
||||||
|
'RZL': '人员入转离项目组',
|
||||||
|
'PJ': '人才评鉴项目组',
|
||||||
|
'HW': '会务服务项目组',
|
||||||
|
'GJ': '国际化服务项目组',
|
||||||
|
'GZ': '管理咨询项目组',
|
||||||
|
'KF': '客户服务室',
|
||||||
|
'FL': '福利业务项目组',
|
||||||
|
'FW': '法务合规中心',
|
||||||
|
'SJ': '数字化与交付项目组',
|
||||||
|
'KJ': '科技培训项目组',
|
||||||
|
'JG': '经营管理项目组',
|
||||||
|
'JN': '技能培训项目组',
|
||||||
|
'DJ': '党建培训项目组',
|
||||||
|
'JH': '计划核定项目组',
|
||||||
|
'LGB': '老干部服务项目组'
|
||||||
|
};
|
||||||
|
|
||||||
|
return groupMapping[prefix] || '客户服务室';
|
||||||
|
};
|
||||||
|
|
||||||
|
const zhuan_ye_zu = getProjectGroupByProjectId(item.projectNumber);
|
||||||
|
|
||||||
|
// 根据新的协议类型显示逻辑
|
||||||
|
let agreementTypeName = '';
|
||||||
|
if (item.agreementType === '2' || item.agreementType === '4') {
|
||||||
|
agreementTypeName = '独立协议';
|
||||||
|
} else if (item.agreementType === '1' || item.agreementType === '3') {
|
||||||
|
agreementTypeName = '大协议';
|
||||||
|
} else {
|
||||||
|
agreementTypeName = AGREEMENT_CODE_TO_NAME[item.agreementType as keyof typeof AGREEMENT_TYPE_NAME] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除这行:const zhuan_ye_zu = XIANGMU_ZUBIE_MAP[item.applicationDepartment || ''] || '项目组别匹配失败';
|
||||||
|
// 集团内外和专业组
|
||||||
|
return {
|
||||||
|
"开票作业序号": item.invoiceJobNumber || '',
|
||||||
|
"数据ID": item.dataId,
|
||||||
|
"项目编号": item.projectNumber || '',
|
||||||
|
"单位": processed.customerCompanyName || '',
|
||||||
|
// 移除"集团内外"
|
||||||
|
"票号": item.piao_hao || '',
|
||||||
|
"不含税金额": buHanShuiAmount,
|
||||||
|
"税额": AllAmount,
|
||||||
|
"含税金额": hanShuiAmount,
|
||||||
|
"发票类型": item.invoiceType || '',
|
||||||
|
"申请人": item.applicant || '',
|
||||||
|
"专业组": zhuan_ye_zu || '',
|
||||||
|
"开票日期": item.kai_piao_ri_qi || '',
|
||||||
|
"账期": item.zhang_qi || '',
|
||||||
|
"回款日期": item.hui_kuan_ri_qi || '',
|
||||||
|
"回款金额": item.hui_kuan_jin_e || '',
|
||||||
|
"统一社会信用代码": processed.tong_yi_she_hui_xin_yong_dai_ma || '',
|
||||||
|
"开户行名称": processed.bankName || '',
|
||||||
|
"银行账号": processed.bankAccount || '',
|
||||||
|
"业务内容": businessContent,
|
||||||
|
"协议类型": agreementTypeName // 添加协议类型字段
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert data to worksheet
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(modifiedInvoiceData, {
|
||||||
|
header: [
|
||||||
|
"开票作业序号",
|
||||||
|
"数据ID",
|
||||||
|
"项目编号",
|
||||||
|
"单位",
|
||||||
|
// 移除"集团内外"
|
||||||
|
"票号",
|
||||||
|
"不含税金额",
|
||||||
|
"税额",
|
||||||
|
"含税金额",
|
||||||
|
"发票类型",
|
||||||
|
"申请人",
|
||||||
|
"专业组",
|
||||||
|
"开票日期",
|
||||||
|
"账期",
|
||||||
|
"回款日期",
|
||||||
|
"回款金额",
|
||||||
|
"统一社会信用代码",
|
||||||
|
"开户行名称",
|
||||||
|
"银行账号",
|
||||||
|
"业务内容",
|
||||||
|
"协议类型" // 添加协议类型列
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
||||||
|
const cols = [];
|
||||||
|
for (let i = 0; i <= range.e.c; i++) {
|
||||||
|
const colHeader = worksheet[XLSX.utils.encode_cell({ r: 0, c: i })]?.v;
|
||||||
|
cols.push({ wch: COLUMN_WIDTHS[colHeader] || 15 });
|
||||||
|
}
|
||||||
|
worksheet['!cols'] = cols;
|
||||||
|
|
||||||
|
// 设置表头样式和单元格格式
|
||||||
|
for (let R = range.s.r; R <= range.e.r; R++) {
|
||||||
|
for (let C = range.s.c; C <= range.e.c; C++) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
|
||||||
|
if (R === 0) {
|
||||||
|
// 表头行
|
||||||
|
worksheet[cellAddress].s = HEADER_STYLE;
|
||||||
|
} else {
|
||||||
|
// 数据行
|
||||||
|
worksheet[cellAddress].s = CELL_STYLE;
|
||||||
|
}
|
||||||
|
// 删除这里的协议类型计算逻辑,因为已经移到映射函数中
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new workbook and append the worksheet
|
||||||
|
const workbook = XLSX.utils.book_new()
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, '开票申请数据')
|
||||||
|
|
||||||
|
const buffer = XLSXStyle.write(workbook, { type: 'buffer', bookType: 'xlsx', cellStyles: true })
|
||||||
|
// Set headers for file download
|
||||||
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=invoice-todolist.xlsx')
|
||||||
|
// Send the Excel file
|
||||||
|
res.write(buffer)
|
||||||
|
res.end()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting invoice data:', error)
|
||||||
|
res.status(500).json({ message: 'Internal server error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
402
src/pages/api/approval/exportInvoiceDataFile.ts
Normal file
402
src/pages/api/approval/exportInvoiceDataFile.ts
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import archiver from 'archiver'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import XLSXStyle from 'xlsx-style';
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import AuthClient from '@/api/dingding/auth'
|
||||||
|
import { getHanShuiAmount, getBuHanShuiAmount, getAllAmount } from '@/pages/api/approval/util'
|
||||||
|
import { AGREEMENT_TYPE_CODE, AGREEMENT_TYPES, XIANGMU_ZUBIE_MAP } from '@/CONSTANTS'
|
||||||
|
import { downloadAttachments, processInvoiceDataItem } from '@/lib/util'
|
||||||
|
|
||||||
|
// 定义协议类型名称映射
|
||||||
|
const AGREEMENT_CODE_TO_NAME = {
|
||||||
|
'1': '大协议',
|
||||||
|
'2': '独立协议',
|
||||||
|
'3': '大协议',
|
||||||
|
'4': '独立协议'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义列宽配置
|
||||||
|
// 列宽:增加“开票作业序号”,移除“集团内外”
|
||||||
|
const COLUMN_WIDTHS = {
|
||||||
|
"开票作业序号": 15,
|
||||||
|
"数据ID": 20,
|
||||||
|
"项目编号": 15,
|
||||||
|
"单位": 25,
|
||||||
|
// 移除“集团内外”
|
||||||
|
"票号": 15,
|
||||||
|
"不含税金额": 15,
|
||||||
|
"税额": 15,
|
||||||
|
"含税金额": 15,
|
||||||
|
"发票类型": 20,
|
||||||
|
"申请人": 15,
|
||||||
|
"专业组": 15,
|
||||||
|
"开票日期": 15,
|
||||||
|
"账期": 15,
|
||||||
|
"回款日期": 15,
|
||||||
|
"回款金额": 15,
|
||||||
|
"统一社会信用代码": 25,
|
||||||
|
"开户行名称": 25,
|
||||||
|
"银行账号": 25,
|
||||||
|
"业务内容": 30
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义表头样式
|
||||||
|
const HEADER_STYLE = {
|
||||||
|
fill: {
|
||||||
|
fgColor: { rgb: "bce672" } // 蓝色背景
|
||||||
|
},
|
||||||
|
font: {
|
||||||
|
color: { rgb: "000000" }, // 白色字体
|
||||||
|
bold: true
|
||||||
|
},
|
||||||
|
alignment: {
|
||||||
|
horizontal: "center",
|
||||||
|
vertical: "center"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义数据单元格样式
|
||||||
|
const CELL_STYLE = {
|
||||||
|
alignment: {
|
||||||
|
horizontal: "left",
|
||||||
|
vertical: "center"
|
||||||
|
},
|
||||||
|
numFmt: "@"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ message: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取access token,实际实现中需要从请求上下文或认证系统获取
|
||||||
|
const accessToken = await AuthClient.getAccessToken();
|
||||||
|
|
||||||
|
const { ids } = req.body
|
||||||
|
console.log('Received IDs:', ids);
|
||||||
|
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||||
|
return res.status(400).json({ message: 'Invalid or missing IDs array' })
|
||||||
|
}
|
||||||
|
if (ids.length > 1000) {
|
||||||
|
return res.status(400).json({ message: 'Too many IDs requested. Maximum 1000 IDs allowed.' })
|
||||||
|
}
|
||||||
|
console.log('Fetching data for IDs:', ids);
|
||||||
|
const invoiceData = await prismaClient.invoice_application_match_view.findMany({
|
||||||
|
where: {
|
||||||
|
dataId: {
|
||||||
|
in: ids
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 预排序(视图字段为数字时有效;为字符串时按字典序,下面还有兜底排序)
|
||||||
|
orderBy: { invoiceJobNumber: 'asc' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提取大协议的dataId
|
||||||
|
const largeAgreementIds = invoiceData
|
||||||
|
.filter(item => item.agreementType === '1')
|
||||||
|
.map(item => item.dataId);
|
||||||
|
|
||||||
|
// 从invoice_amount_details获取大协议的业务内容
|
||||||
|
const largeAgreementDetails = await prismaClient.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: {
|
||||||
|
in: largeAgreementIds
|
||||||
|
},
|
||||||
|
agreementType: AGREEMENT_TYPE_CODE[AGREEMENT_TYPES.LARGE]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建一个映射,存储每个dataid对应的多个business_content
|
||||||
|
const businessContentMap = new Map<string, string[]>();
|
||||||
|
largeAgreementDetails.forEach((detail, idx) => {
|
||||||
|
if (detail.dataid && detail.business_content) {
|
||||||
|
if (!businessContentMap.has(detail.dataid)) {
|
||||||
|
businessContentMap.set(detail.dataid, []);
|
||||||
|
}
|
||||||
|
businessContentMap.get(detail.dataid)?.push(`${idx}. [${detail.project_name}]${detail.business_content}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('拉取开票申请数据数量:', invoiceData.length);
|
||||||
|
const modifiedInvoiceData = invoiceData
|
||||||
|
.map(item => {
|
||||||
|
const processed = processInvoiceDataItem(item)
|
||||||
|
const buHanShuiAmount = getBuHanShuiAmount(item)
|
||||||
|
const hanShuiAmount = getHanShuiAmount(item)
|
||||||
|
const AllAmount = getAllAmount(buHanShuiAmount, hanShuiAmount);
|
||||||
|
|
||||||
|
// 业务内容统一取开票事由
|
||||||
|
const businessContent = item.kai_piao_shi_you || '';
|
||||||
|
|
||||||
|
// 根据新的协议类型显示逻辑
|
||||||
|
let agreementTypeName = '';
|
||||||
|
if (item.agreementType === '2' || item.agreementType === '4') {
|
||||||
|
agreementTypeName = '独立协议';
|
||||||
|
} else if (item.agreementType === '1' || item.agreementType === '3') {
|
||||||
|
agreementTypeName = '大协议';
|
||||||
|
} else {
|
||||||
|
agreementTypeName = AGREEMENT_CODE_TO_NAME[item.agreementType as keyof typeof AGREEMENT_CODE_TO_NAME] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 专业组逻辑:提取项目编号的所有字母部分映射,如果没有项目编号则为客户服务室
|
||||||
|
const getProjectGroupByProjectId = (projectId: string | null | undefined): string => {
|
||||||
|
if (!projectId) {
|
||||||
|
return '客户服务室';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目编号的所有字母部分(匹配连续的字母字符,不限位数)
|
||||||
|
const letterMatch = projectId.match(/^[A-Za-z]+/);
|
||||||
|
const prefix = letterMatch ? letterMatch[0] : '';
|
||||||
|
|
||||||
|
// 根据字母前缀映射专业组
|
||||||
|
const groupMapping: { [key: string]: string } = {
|
||||||
|
'YY': '运营室',
|
||||||
|
'ZP': '人员招聘项目组',
|
||||||
|
'ZW': '总务服务项目组',
|
||||||
|
'XC': '薪酬核算项目组',
|
||||||
|
'TX': '退休衔接服务项目组',
|
||||||
|
'TC': '统筹支持项目组',
|
||||||
|
'RZL': '人员入转离项目组',
|
||||||
|
'PJ': '人才评鉴项目组',
|
||||||
|
'HW': '会务服务项目组',
|
||||||
|
'GJ': '国际化服务项目组',
|
||||||
|
'GZ': '管理咨询项目组',
|
||||||
|
'KF': '客户服务室',
|
||||||
|
'FL': '福利业务项目组',
|
||||||
|
'FW': '法务合规中心',
|
||||||
|
'SJ': '数字化与交付项目组',
|
||||||
|
'KJ': '科技培训项目组',
|
||||||
|
'JG': '经营管理项目组',
|
||||||
|
'JN': '技能培训项目组',
|
||||||
|
'DJ': '党建培训项目组',
|
||||||
|
'JH': '计划核定项目组',
|
||||||
|
'LGB': '老干部服务项目组'
|
||||||
|
};
|
||||||
|
|
||||||
|
return groupMapping[prefix] || '客户服务室';
|
||||||
|
};
|
||||||
|
|
||||||
|
const zhuan_ye_zu = getProjectGroupByProjectId(item.projectNumber);
|
||||||
|
|
||||||
|
// 账期计算逻辑
|
||||||
|
let zhang_qi = '';
|
||||||
|
const kai_piao_ri_qi = item.kai_piao_ri_qi;
|
||||||
|
const hui_kuan_ri_qi = item.hui_kuan_ri_qi;
|
||||||
|
|
||||||
|
// 优先检查回款日期:如果有回款日期,则没有账期
|
||||||
|
if (hui_kuan_ri_qi) {
|
||||||
|
zhang_qi = '';
|
||||||
|
} else if (kai_piao_ri_qi) {
|
||||||
|
// 只有开票日期:账期 = 当前日期 - 开票日期(单位:天)
|
||||||
|
const kp = dayjs(kai_piao_ri_qi);
|
||||||
|
const now = dayjs();
|
||||||
|
if (kp.isValid()) {
|
||||||
|
const dayDiff = now.diff(kp, 'day');
|
||||||
|
zhang_qi = dayDiff.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"开票作业序号": item.invoiceJobNumber || '',
|
||||||
|
"数据ID": item.dataId,
|
||||||
|
"项目编号": item.projectNumber || '',
|
||||||
|
"单位": processed.customerCompanyName || '',
|
||||||
|
"票号": item.piao_hao || '',
|
||||||
|
"不含税金额": buHanShuiAmount,
|
||||||
|
"税额": AllAmount,
|
||||||
|
"含税金额": hanShuiAmount,
|
||||||
|
"发票类型": item.invoiceType || '',
|
||||||
|
"申请人": item.applicant || '',
|
||||||
|
"专业组": zhuan_ye_zu || '',
|
||||||
|
"开票日期": item.kai_piao_ri_qi || '',
|
||||||
|
"账期": zhang_qi, // 使用计算后的账期值
|
||||||
|
"回款日期": item.hui_kuan_ri_qi || '',
|
||||||
|
"回款金额": item.hui_kuan_jin_e || '',
|
||||||
|
"统一社会信用代码": processed.tong_yi_she_hui_xin_yong_dai_ma || '',
|
||||||
|
"开户行名称": processed.bankName || '',
|
||||||
|
"银行账号": processed.bankAccount || '',
|
||||||
|
"业务内容": businessContent,
|
||||||
|
"协议类型": agreementTypeName
|
||||||
|
};
|
||||||
|
})
|
||||||
|
// 兜底排序:有效数字升序,空值/非数字排最后
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aRaw = a["开票作业序号"];
|
||||||
|
const bRaw = b["开票作业序号"];
|
||||||
|
|
||||||
|
const aEmpty = aRaw === '' || aRaw == null;
|
||||||
|
const bEmpty = bRaw === '' || bRaw == null;
|
||||||
|
if (aEmpty && bEmpty) return 0;
|
||||||
|
if (aEmpty) return 1;
|
||||||
|
if (bEmpty) return -1;
|
||||||
|
|
||||||
|
const aNum = Number(aRaw);
|
||||||
|
const bNum = Number(bRaw);
|
||||||
|
const aNumValid = Number.isFinite(aNum);
|
||||||
|
const bNumValid = Number.isFinite(bNum);
|
||||||
|
if (aNumValid && bNumValid) return aNum - bNum;
|
||||||
|
if (aNumValid) return -1;
|
||||||
|
if (bNumValid) return 1;
|
||||||
|
|
||||||
|
return String(aRaw).localeCompare(String(bRaw), 'zh-Hans-CN', { numeric: true });
|
||||||
|
});
|
||||||
|
// 转为工作表时的表头移除“集团内外”,统一“开票作业序号”
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(modifiedInvoiceData, {
|
||||||
|
header: [
|
||||||
|
"开票作业序号",
|
||||||
|
"数据ID",
|
||||||
|
"项目编号",
|
||||||
|
"单位",
|
||||||
|
// "集团内外" 已移除
|
||||||
|
"票号",
|
||||||
|
"不含税金额",
|
||||||
|
"税额",
|
||||||
|
"含税金额",
|
||||||
|
"发票类型",
|
||||||
|
"申请人",
|
||||||
|
"专业组",
|
||||||
|
"开票日期",
|
||||||
|
"账期",
|
||||||
|
"回款日期",
|
||||||
|
"回款金额",
|
||||||
|
"统一社会信用代码",
|
||||||
|
"开户行名称",
|
||||||
|
"银行账号",
|
||||||
|
"业务内容"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
||||||
|
const cols = [];
|
||||||
|
for (let i = 0; i <= range.e.c; i++) {
|
||||||
|
const colHeader = worksheet[XLSX.utils.encode_cell({ r: 0, c: i })]?.v;
|
||||||
|
cols.push({ wch: COLUMN_WIDTHS[colHeader] || 15 });
|
||||||
|
}
|
||||||
|
worksheet['!cols'] = cols;
|
||||||
|
|
||||||
|
// 设置表头样式和单元格格式
|
||||||
|
for (let R = range.s.r; R <= range.e.r; R++) {
|
||||||
|
for (let C = range.s.c; C <= range.e.c; C++) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
|
||||||
|
if (R === 0) {
|
||||||
|
// 表头行
|
||||||
|
worksheet[cellAddress].s = HEADER_STYLE;
|
||||||
|
} else {
|
||||||
|
// 数据行
|
||||||
|
worksheet[cellAddress].s = CELL_STYLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new workbook and append the worksheet
|
||||||
|
const workbook = XLSX.utils.book_new()
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, '开票申请数据')
|
||||||
|
|
||||||
|
const buffer = XLSXStyle.write(workbook, { type: 'buffer', bookType: 'xlsx', cellStyles: true })
|
||||||
|
|
||||||
|
// 创建日期时间戳文件夹
|
||||||
|
const now = dayjs();
|
||||||
|
const dateStr = now.format('YYYY-MM-DD');
|
||||||
|
const timeStr = now.format('HHmmss');
|
||||||
|
const folderName = `${dateStr}_${timeStr}`;
|
||||||
|
const baseDir = path.join(process.cwd(), 'filebase');
|
||||||
|
const folderPath = path.join(baseDir, folderName);
|
||||||
|
|
||||||
|
// 确保filebase目录存在
|
||||||
|
if (!fs.existsSync(baseDir)) {
|
||||||
|
fs.mkdirSync(baseDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
fs.mkdirSync(folderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存Excel文件到文件夹
|
||||||
|
const excelFileName = '开票申请数据.xlsx';
|
||||||
|
const excelFilePath = path.join(folderPath, excelFileName);
|
||||||
|
fs.writeFileSync(excelFilePath, buffer);
|
||||||
|
|
||||||
|
for (const item of invoiceData) {
|
||||||
|
const invoiceFolderPath = path.join(folderPath, `${item.invoiceJobNumber}-${item.applicant}`)
|
||||||
|
if (item.uploadedContractAttachment) {
|
||||||
|
await downloadAttachments(
|
||||||
|
item.uploadedContractAttachment,
|
||||||
|
invoiceFolderPath,
|
||||||
|
'上传合同及相关附件',
|
||||||
|
item.dataId,
|
||||||
|
accessToken,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理uploadedSealedCostList
|
||||||
|
if (item.uploadedSealedCostListDa) {
|
||||||
|
await downloadAttachments(
|
||||||
|
item.uploadedSealedCostListDa,
|
||||||
|
invoiceFolderPath,
|
||||||
|
'上传费用清单',
|
||||||
|
item.dataId,
|
||||||
|
accessToken,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 处理uploadedSealedCostList
|
||||||
|
if (item.uploadedSealedCostListDu) {
|
||||||
|
await downloadAttachments(
|
||||||
|
item.uploadedSealedCostListDu,
|
||||||
|
invoiceFolderPath,
|
||||||
|
'上传盖章费用清单(独)',
|
||||||
|
item.dataId,
|
||||||
|
accessToken,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建压缩文件
|
||||||
|
const zipFileName = `${folderName}.zip`;
|
||||||
|
const zipFilePath = path.join(process.env.DOWNLOAD_BASE_DIR ?? '', zipFileName);
|
||||||
|
const output = fs.createWriteStream(zipFilePath);
|
||||||
|
const archive = archiver('zip', {
|
||||||
|
zlib: { level: 9 } // 设置压缩级别
|
||||||
|
});
|
||||||
|
|
||||||
|
output.on('error', function (err) {
|
||||||
|
console.error('Error writing zip file:', err);
|
||||||
|
res.status(500).json({ message: 'Error writing zip file' });
|
||||||
|
});
|
||||||
|
// 当归档完成时发送响应
|
||||||
|
output.on('close', function () {
|
||||||
|
console.log(archive.pointer() + ' total bytes');
|
||||||
|
console.log('archiver has been finalized and the output file descriptor has closed.');
|
||||||
|
console.log(`${process.env.DOWNLOAD_BASE_URL ?? ''}/${zipFileName}`)
|
||||||
|
res.status(200).json({ code: 0, "error": null, message: '文件已经被妥善下载。', data: { zipFilePath: `${process.env.DOWNLOAD_BASE_URL ?? ''}/${zipFileName}` } });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理归档错误
|
||||||
|
archive.on('error', function (err) {
|
||||||
|
console.error('Error creating zip file:', err);
|
||||||
|
res.status(500).json({ message: 'Error creating zip file' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始归档过程
|
||||||
|
archive.pipe(output);
|
||||||
|
archive.directory(folderPath, false); // 将整个文件夹添加到归档中 TODO 这个foldername 是个啥
|
||||||
|
archive.finalize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting invoice data:', error)
|
||||||
|
res.status(500).json({ message: 'Internal server error', error })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
135
src/pages/api/approval/importInvoiceApprovalData.ts
Normal file
135
src/pages/api/approval/importInvoiceApprovalData.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { InvoiceApplicationItem, InvoiceApplicationDBItem, InvoiceApplicationAmountDetails } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { AGREEMENT_TYPE_CODE, AGREEMENT_TYPE_NAME } from '@/CONSTANTS';
|
||||||
|
import { ExcelToMySQLFieldMapping } from './util'
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: InvoiceApplicationItem[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
// 3.1 从数据库获取已存在的dataId
|
||||||
|
const existingRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
select: {
|
||||||
|
dataId: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const existingDataIds = new Set(existingRecords.map(record => record.dataId));
|
||||||
|
|
||||||
|
let dataKV: {
|
||||||
|
[key: string]: InvoiceApplicationDBItem;
|
||||||
|
} = {};
|
||||||
|
const InvoiceAmountList = []
|
||||||
|
|
||||||
|
for (let i = 1; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const key = rawItem['数据id'];
|
||||||
|
|
||||||
|
// 3.2 跳过数据库中已存在的数据
|
||||||
|
if (existingDataIds.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agreementTypeCode = AGREEMENT_TYPE_CODE[AGREEMENT_TYPE_NAME[rawItem['开票类型']]]
|
||||||
|
if (!agreementTypeCode) {
|
||||||
|
throw new Error(`第${i}行开票日期不识别`)
|
||||||
|
}
|
||||||
|
const bigInvoiceAmount: InvoiceApplicationAmountDetails = {
|
||||||
|
dataid: rawItem['数据id'],
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
project_number: '',
|
||||||
|
business_group_name: rawItem['开票金额明细'],
|
||||||
|
project_name: rawItem['开票金额明细_1'],
|
||||||
|
business_content: rawItem['开票金额明细_2'],
|
||||||
|
invoice_amount: rawItem['开票金额明细_3'],
|
||||||
|
}
|
||||||
|
|
||||||
|
const multiProjectMergeInvoice: InvoiceApplicationAmountDetails = {
|
||||||
|
dataid: rawItem['数据id'],
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
project_number: rawItem['多项目合并开票请添加'],
|
||||||
|
project_name: rawItem['多项目合并开票请添加_1'],
|
||||||
|
business_group_name: rawItem['多项目合并开票请添加_2'],
|
||||||
|
business_content: '',
|
||||||
|
invoice_amount: rawItem['多项目合并开票请添加_3'],
|
||||||
|
}
|
||||||
|
if (!dataKV[key]) {
|
||||||
|
dataKV[key] = {
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
BigInvoiceAmountList: [], // InvoiceApplicationDBItem 特有字段
|
||||||
|
multiProjectMergeInvoice: [],
|
||||||
|
};
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
dataKV[key][dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
if (bigInvoiceAmount.business_group_name && bigInvoiceAmount.invoice_amount && bigInvoiceAmount.project_name) {
|
||||||
|
dataKV[key].BigInvoiceAmountList.push(bigInvoiceAmount)
|
||||||
|
InvoiceAmountList.push(bigInvoiceAmount)
|
||||||
|
}
|
||||||
|
if (multiProjectMergeInvoice.project_number && multiProjectMergeInvoice.invoice_amount && multiProjectMergeInvoice.project_name) {
|
||||||
|
dataKV[key].multiProjectMergeInvoice.push(multiProjectMergeInvoice)
|
||||||
|
InvoiceAmountList.push(multiProjectMergeInvoice)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (bigInvoiceAmount.business_group_name && bigInvoiceAmount.invoice_amount && bigInvoiceAmount.project_name) {
|
||||||
|
dataKV[key].BigInvoiceAmountList.push(bigInvoiceAmount)
|
||||||
|
InvoiceAmountList.push(bigInvoiceAmount)
|
||||||
|
}
|
||||||
|
if (multiProjectMergeInvoice.project_number && multiProjectMergeInvoice.invoice_amount && multiProjectMergeInvoice.project_name) {
|
||||||
|
dataKV[key].multiProjectMergeInvoice.push(multiProjectMergeInvoice)
|
||||||
|
InvoiceAmountList.push(multiProjectMergeInvoice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await prismaClient.$transaction([
|
||||||
|
prismaClient.invoice_amount_details.createMany({
|
||||||
|
data: InvoiceAmountList,
|
||||||
|
skipDuplicates: false,
|
||||||
|
}),
|
||||||
|
prismaClient.dingding_oa_invoice_application.createMany({
|
||||||
|
data: Object.values(dataKV).map(item => {
|
||||||
|
const mergedProjectNumbers = Array.from(new Set((item.multiProjectMergeInvoice || []).map(d => d.project_number).filter(Boolean)));
|
||||||
|
if (mergedProjectNumbers.length > 1) {
|
||||||
|
item.projectNumber = mergedProjectNumbers.join(',');
|
||||||
|
item.is_multi_project_merge = '是';
|
||||||
|
} else if (mergedProjectNumbers.length === 1) {
|
||||||
|
item.projectNumber = mergedProjectNumbers[0];
|
||||||
|
}
|
||||||
|
const { BigInvoiceAmountList, multiProjectMergeInvoice, ...rest } = item;
|
||||||
|
return rest;
|
||||||
|
}),
|
||||||
|
skipDuplicates: false,
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: `钉钉开票申请数据导入成功,共导入${Object.values(dataKV).length}条数据,${InvoiceAmountList.length}条开票金额明细数据`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
156
src/pages/api/approval/util.ts
Normal file
156
src/pages/api/approval/util.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// 在文件顶部,导入语句之后添加
|
||||||
|
import { AGREEMENT_CODE_TO_TYPE, AGREEMENT_TYPE_NAME, AGREEMENT_TYPES } from '@/CONSTANTS'
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
export const ExcelToMySQLFieldMapping: {
|
||||||
|
[k: string]: string
|
||||||
|
} = {
|
||||||
|
'数据id': 'dataId',
|
||||||
|
'申请日期': 'applicationDate',
|
||||||
|
'申请人': 'applicant',
|
||||||
|
'申请部门': 'applicationDepartment',
|
||||||
|
'项目组别': 'xiang_mu_zu_bie',
|
||||||
|
'预计报销第三方成本(不含税,元)': 'yuJiBaoXiaoDiSanFangChengBenYuan',
|
||||||
|
'业务开始时间': 'businessStartTime',
|
||||||
|
'业务结束时间': 'businessEndTime',
|
||||||
|
'销售合同编号': 'contractNumber',
|
||||||
|
'销售合同名称': 'contractName',
|
||||||
|
'合同原件扫描件(盖章版)': 'uploadedContractAttachment',
|
||||||
|
'项目编号': 'projectNumber',
|
||||||
|
'是否含税': 'is_tax_inclusive',
|
||||||
|
'开票税率': 'invoice_tax_rate',
|
||||||
|
'是否多项目合并': 'is_multi_project_merge',
|
||||||
|
'上传盖章费用清单': 'uploadedSealedCostList',
|
||||||
|
'客户单位名称': 'customerCompanyName',
|
||||||
|
'统一社会信用代码': 'tong_yi_she_hui_xin_yong_dai_ma',
|
||||||
|
'开户行名称': 'bankName',
|
||||||
|
'银行账号': 'bankAccount',
|
||||||
|
'开票税目': 'invoice_tax_category',
|
||||||
|
'业务内容(备注)': 'business_content_du',
|
||||||
|
'开票金额(元)独': 'invoice_amount_yuan_du',
|
||||||
|
'不含税金额(元)独': 'bu_han_shui_jin_e_yuan_du',
|
||||||
|
'不含税金额(元)独(大写)': 'bu_han_shui_jin_e_yuan_du_da_xie',
|
||||||
|
'含税金额(元)独': 'han_shui_jin_e_yuan_du',
|
||||||
|
'含税金额(元)独(大写)': 'han_shui_jin_e_yuan_du_da_xie',
|
||||||
|
'不含税金额(元)多': 'bu_han_shui_jin_e_yuan_duo',
|
||||||
|
'不含税金额(元)多(大写)': 'bu_han_shui_jin_e_yuan_duo_da_xie',
|
||||||
|
'含税金额(元)多': 'han_shui_jin_e_yuan_duo',
|
||||||
|
'含税金额(元)多(大写)': 'han_shui_jin_e_yuan_duo_da_xie',
|
||||||
|
'不含税金额(元)大': 'bu_han_shui_jin_e_yuan_da',
|
||||||
|
'含税总金额(元)大': 'han_shui_zong_jin_e_yuan_da',
|
||||||
|
'不含税金额(元)(大+独)': 'bu_han_shui_jin_e_yuan_da_du',
|
||||||
|
'不含税金额(元)(大+独)(大写)': 'bu_han_shui_jin_e_yuan_da_du_da_xie',
|
||||||
|
'含税总金额(元)(大+独)': 'han_shui_zong_jin_e_yuan_da_du',
|
||||||
|
'含税总金额(元)(大+独)(大写)': 'han_shui_zong_jin_e_yuan_da_du_da_xie',
|
||||||
|
'审批编号': 'approvalNumber',
|
||||||
|
'创建时间': 'creationTime',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'currentResponsiblePerson',
|
||||||
|
'审批结果': 'approvalResult',
|
||||||
|
'审批状态': 'approvalStatus',
|
||||||
|
'更新时间': 'updateTime',
|
||||||
|
'创建人部门': 'creatorDepartment',
|
||||||
|
'审批单标题': 'approvalFormTitle',
|
||||||
|
'历史审批人': 'historicalApprovers',
|
||||||
|
'耗时(时:分:秒)': 'timeConsumed',
|
||||||
|
'审批记录': 'approvalRecord',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export function getBuHanShuiAmount(item: any) {
|
||||||
|
const normalize = (val: any): string => {
|
||||||
|
const s = String(val ?? '').trim()
|
||||||
|
if (!s) return ''
|
||||||
|
const neg = /^\(.+\)$/.test(s)
|
||||||
|
let cleaned = s.replace(/[,\s,]/g, '')
|
||||||
|
cleaned = cleaned.replace(/[¥¥元]/g, '')
|
||||||
|
cleaned = cleaned.replace(/^[\(]|[\)]$/g, '')
|
||||||
|
const num = new BigNumber(cleaned)
|
||||||
|
if (!num.isFinite()) return ''
|
||||||
|
return neg ? num.negated().toString() : num.toString()
|
||||||
|
}
|
||||||
|
const raw = item?.agreementType
|
||||||
|
const code = raw == null ? '' : String(raw)
|
||||||
|
const agreementType = AGREEMENT_CODE_TO_TYPE[code] || AGREEMENT_TYPE_NAME[code] || code
|
||||||
|
const isSingleContractMultiCustomer = ['是', '1', 'true', 'TRUE', 'yes', 'YES'].includes(String(item?.is_single_contract_multi_customer ?? '').trim())
|
||||||
|
if (isSingleContractMultiCustomer) {
|
||||||
|
const singleAmount = normalize(item.bu_han_shui_jin_e_yuan_du)
|
||||||
|
if (singleAmount !== '') {
|
||||||
|
return singleAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 这个逻辑一定在前否则先过独立协议了,这个逻辑永远走不到
|
||||||
|
if (agreementType === AGREEMENT_TYPES.INDEPENDENT && item.is_multi_project_merge === '是') {
|
||||||
|
return normalize(item.bu_han_shui_jin_e_yuan_duo);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.LARGE) {
|
||||||
|
return normalize(item.bu_han_shui_jin_e_yuan_da);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.INDEPENDENT) {
|
||||||
|
return normalize(item.bu_han_shui_jin_e_yuan_du);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.LARGE_INDEPENDENT) {
|
||||||
|
return normalize(item.bu_han_shui_jin_e_yuan_da_du);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.OTHER) {
|
||||||
|
return normalize(item.bu_han_shui_jin_e_yuan_other);
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHanShuiAmount(item: any) {
|
||||||
|
const normalize = (val: any): string => {
|
||||||
|
const s = String(val ?? '').trim()
|
||||||
|
if (!s) return ''
|
||||||
|
const neg = /^\(.+\)$/.test(s)
|
||||||
|
let cleaned = s.replace(/[,\s,]/g, '')
|
||||||
|
cleaned = cleaned.replace(/[¥¥元]/g, '')
|
||||||
|
cleaned = cleaned.replace(/^[\(]|[\)]$/g, '')
|
||||||
|
const num = new BigNumber(cleaned)
|
||||||
|
if (!num.isFinite()) return ''
|
||||||
|
return neg ? num.negated().toString() : num.toString()
|
||||||
|
}
|
||||||
|
const raw = item?.agreementType
|
||||||
|
const code = raw == null ? '' : String(raw)
|
||||||
|
const agreementType = AGREEMENT_CODE_TO_TYPE[code] || AGREEMENT_TYPE_NAME[code] || code
|
||||||
|
const isSingleContractMultiCustomer = ['是', '1', 'true', 'TRUE', 'yes', 'YES'].includes(String(item?.is_single_contract_multi_customer ?? '').trim())
|
||||||
|
if (isSingleContractMultiCustomer) {
|
||||||
|
const singleAmount = normalize(item.han_shui_jin_e_yuan_du)
|
||||||
|
if (singleAmount !== '') {
|
||||||
|
return singleAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 这个逻辑一定在前否则先过独立协议了,这个逻辑永远走不到
|
||||||
|
if (agreementType === AGREEMENT_TYPES.INDEPENDENT && item.is_multi_project_merge === '是') {
|
||||||
|
return normalize(item.han_shui_jin_e_yuan_duo);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.LARGE) {
|
||||||
|
return normalize(item.han_shui_jin_e_yuan_da);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.INDEPENDENT) {
|
||||||
|
return normalize(item.han_shui_jin_e_yuan_du);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.LARGE_INDEPENDENT) {
|
||||||
|
return normalize(item.han_shui_jin_e_yuan_da_du);
|
||||||
|
}
|
||||||
|
if (agreementType === AGREEMENT_TYPES.OTHER) {
|
||||||
|
return normalize(item.han_shui_jin_e_yuan_other);
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function getAllAmount(buHanShuiAmount: string, hanShuiAmount: string): string {
|
||||||
|
// 使用bignumber.js处理金额计算,避免浮点数精度问题
|
||||||
|
const buHanShuiNum = new BigNumber(buHanShuiAmount || '0');
|
||||||
|
const hanShuiNum = new BigNumber(hanShuiAmount || '0');
|
||||||
|
|
||||||
|
// 检查输入是否为有效数字
|
||||||
|
if (!buHanShuiNum.isFinite() || !hanShuiNum.isFinite()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
// 执行减法运算并返回字符串结果(含税金额 - 不含税金额)
|
||||||
|
return hanShuiNum.minus(buHanShuiNum).toString();
|
||||||
|
}
|
||||||
|
|
||||||
100
src/pages/api/business_details/export.ts
Normal file
100
src/pages/api/business_details/export.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import XLSXStyle from 'xlsx-style';
|
||||||
|
import { getBussinessDetailsList } from '../project/util'
|
||||||
|
import { getProjectIdsByInvoiceDate } from './util'
|
||||||
|
|
||||||
|
// 定义表头样式
|
||||||
|
const HEADER_STYLE = {
|
||||||
|
fill: {
|
||||||
|
fgColor: { rgb: "bce672" }
|
||||||
|
},
|
||||||
|
font: {
|
||||||
|
color: { rgb: "000000" },
|
||||||
|
bold: true
|
||||||
|
},
|
||||||
|
alignment: {
|
||||||
|
horizontal: "center",
|
||||||
|
vertical: "center"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义数据单元格样式
|
||||||
|
const CELL_STYLE = {
|
||||||
|
alignment: {
|
||||||
|
horizontal: "left",
|
||||||
|
vertical: "center"
|
||||||
|
},
|
||||||
|
numFmt: "@"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ message: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { kai_piao_ri_qi, project_number } = req.body;
|
||||||
|
|
||||||
|
// 根据开票日期(支持单日或范围)与项目编号,确定要导出的项目集合
|
||||||
|
let projectIdsFromInvoice: string[] = [];
|
||||||
|
if (kai_piao_ri_qi) {
|
||||||
|
projectIdsFromInvoice = await getProjectIdsByInvoiceDate(kai_piao_ri_qi);
|
||||||
|
}
|
||||||
|
if (project_number) {
|
||||||
|
projectIdsFromInvoice.push(project_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询项目
|
||||||
|
const projects = await prismaClient.project.findMany({
|
||||||
|
where: projectIdsFromInvoice.length > 0 ? { project_id: { in: projectIdsFromInvoice } } : undefined,
|
||||||
|
orderBy: {
|
||||||
|
project_id: 'desc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成业务明细数据(Excel 行数据)
|
||||||
|
const excelDatas = await getBussinessDetailsList(projects);
|
||||||
|
|
||||||
|
// 若没有数据,返回空文件(也可改为返回提示)
|
||||||
|
const headerKeys = Object.keys(excelDatas?.[0] ?? {});
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(excelDatas, {
|
||||||
|
header: headerKeys.length > 0 ? headerKeys : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置样式
|
||||||
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
||||||
|
// 统一列宽(可按需改为按列名定制)
|
||||||
|
const cols = [];
|
||||||
|
for (let i = 0; i <= range.e.c; i++) {
|
||||||
|
cols.push({ wch: 18 });
|
||||||
|
}
|
||||||
|
worksheet['!cols'] = cols;
|
||||||
|
|
||||||
|
for (let R = range.s.r; R <= range.e.r; R++) {
|
||||||
|
for (let C = range.s.c; C <= range.e.c; C++) {
|
||||||
|
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C });
|
||||||
|
if (worksheet[cellAddress]) {
|
||||||
|
worksheet[cellAddress].s = R === 0 ? HEADER_STYLE : CELL_STYLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成工作簿并输出
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(workbook, worksheet, '业务明细数据');
|
||||||
|
|
||||||
|
const buffer = XLSXStyle.write(workbook, { type: 'buffer', bookType: 'xlsx', cellStyles: true });
|
||||||
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=business-details.xlsx');
|
||||||
|
res.write(buffer);
|
||||||
|
res.end();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting business details:', error);
|
||||||
|
res.status(500).json({ message: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/pages/api/business_details/get.ts
Normal file
150
src/pages/api/business_details/get.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Prisma } from '@prisma/client'
|
||||||
|
import { AGREEMENT_TYPES, AGREEMENT_TYPE_NAME, AGREEMENT_TYPE_CODE } from '@/CONSTANTS'
|
||||||
|
import type { InvoiceDataItem, ProcessedInvoiceDataItem } from '@/pages/api/type'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { getBussinessDetailsList } from '../project/util'
|
||||||
|
import { getProjectIdsByInvoiceDate } from './util'
|
||||||
|
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any;
|
||||||
|
pagination?: {
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 这里是获取业务明细表的接口
|
||||||
|
const input: any = (req.body && Object.keys(req.body).length > 0) ? req.body : req.query;
|
||||||
|
const { page = 1, pageSize = 10, kai_piao_ri_qi } = input;
|
||||||
|
const project_number = typeof input?.project_number === 'string' ? input.project_number.trim() : input?.project_number;
|
||||||
|
const project_leader = typeof input?.project_leader === 'string' ? input.project_leader.trim() : input?.project_leader;
|
||||||
|
const project_name = typeof input?.project_name === 'string' ? input.project_name.trim() : input?.project_name;
|
||||||
|
const pageNum = Math.max(1, Number(page));
|
||||||
|
const size = Math.max(1, Number(pageSize));
|
||||||
|
|
||||||
|
const invoiceProjectIdsRaw: string[] = kai_piao_ri_qi ? await getProjectIdsByInvoiceDate(kai_piao_ri_qi) : [];
|
||||||
|
const invoiceProjectIds = Array.from(new Set((invoiceProjectIdsRaw || []).map(s => String(s).trim()).filter(Boolean)));
|
||||||
|
const projectIdsForFilter = [
|
||||||
|
...invoiceProjectIds,
|
||||||
|
...(project_number ? [String(project_number).trim()].filter(Boolean) : [])
|
||||||
|
];
|
||||||
|
if (kai_piao_ri_qi && invoiceProjectIds.length === 0 && !project_number) {
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取成功',
|
||||||
|
data: {
|
||||||
|
data: [],
|
||||||
|
pagination: {
|
||||||
|
total: 0,
|
||||||
|
page: pageNum,
|
||||||
|
pageSize: size,
|
||||||
|
totalPages: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目总数
|
||||||
|
const countWhere: any = {};
|
||||||
|
if (projectIdsForFilter.length > 0) {
|
||||||
|
countWhere.project_id = { in: projectIdsForFilter };
|
||||||
|
}
|
||||||
|
if (project_leader) {
|
||||||
|
countWhere.project_leader = project_leader;
|
||||||
|
}
|
||||||
|
if (project_name) {
|
||||||
|
countWhere.project_name = { contains: project_name };
|
||||||
|
}
|
||||||
|
const total = await prismaClient.project.count(Object.keys(countWhere).length > 0 ? { where: countWhere } : undefined);
|
||||||
|
|
||||||
|
// 分页查询项目
|
||||||
|
const conditions: any[] = [];
|
||||||
|
if (projectIdsForFilter.length > 0) {
|
||||||
|
conditions.push(Prisma.sql`project_id IN (${Prisma.join(projectIdsForFilter)})`);
|
||||||
|
}
|
||||||
|
if (project_leader) {
|
||||||
|
conditions.push(Prisma.sql`project_leader = ${project_leader}`);
|
||||||
|
}
|
||||||
|
if (project_name) {
|
||||||
|
conditions.push(Prisma.sql`project_name LIKE ${'%' + project_name + '%'}`);
|
||||||
|
}
|
||||||
|
let whereClause = Prisma.sql``;
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
whereClause = Prisma.sql`WHERE ${conditions[0]}`;
|
||||||
|
for (let i = 1; i < conditions.length; i++) {
|
||||||
|
whereClause = Prisma.sql`${whereClause} AND ${conditions[i]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const projects: any[] = await prismaClient.$queryRaw(Prisma.sql`
|
||||||
|
SELECT * FROM project
|
||||||
|
${whereClause}
|
||||||
|
ORDER BY CAST(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
project_id,
|
||||||
|
'A',''),'B',''),'C',''),'D',''),'E',''),'F',''),'G',''),'H',''),'I',''),'J',''),
|
||||||
|
'K',''),'L',''),'M',''),'N',''),'O',''),'P',''),'Q',''),'R',''),'S',''),'T',''),
|
||||||
|
'U',''),'V',''),'W',''),'X',''),'Y',''),'Z',''),
|
||||||
|
'a',''),'b',''),'c',''),'d',''),'e',''),'f',''),'g',''),'h',''),'i',''),'j',''),
|
||||||
|
'k',''),'l',''),'m',''),'n',''),'o',''),'p',''),'q',''),'r',''),'s',''),'t',''),
|
||||||
|
'u',''),'v',''),'w',''),'x',''),'y',''),'z','')
|
||||||
|
AS UNSIGNED) DESC
|
||||||
|
LIMIT ${size} OFFSET ${pageNum - 1 >= 0 ? (pageNum - 1) * size : 0}
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 后端已按项目编号倒序查询,无需再次排序
|
||||||
|
|
||||||
|
const excelDatas = await getBussinessDetailsList(projects, kai_piao_ri_qi);
|
||||||
|
const startIndex = (pageNum - 1) * size;
|
||||||
|
const excelDatasWithSeq = excelDatas.map((row, idx) => ({
|
||||||
|
...row,
|
||||||
|
'序号': startIndex + idx + 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 构造分页信息
|
||||||
|
const totalPages = Math.ceil(total / size);
|
||||||
|
const pagination = {
|
||||||
|
total,
|
||||||
|
page: pageNum,
|
||||||
|
pageSize: size,
|
||||||
|
totalPages
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取成功',
|
||||||
|
data: {
|
||||||
|
data: excelDatasWithSeq,
|
||||||
|
pagination
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
res.status(500).json({ message: '获取数据时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
134
src/pages/api/business_details/getLeagerALL.ts
Normal file
134
src/pages/api/business_details/getLeagerALL.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Prisma } from '@prisma/client'
|
||||||
|
import { AGREEMENT_TYPES, AGREEMENT_TYPE_NAME, AGREEMENT_TYPE_CODE } from '@/CONSTANTS'
|
||||||
|
import type { InvoiceDataItem, ProcessedInvoiceDataItem } from '@/pages/api/type'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { getBussinessDetailsList } from '../project/utilCopy'
|
||||||
|
import { getProjectIdsByInvoiceDate } from './util'
|
||||||
|
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any;
|
||||||
|
pagination?: {
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 这里是获取业务明细表的接口
|
||||||
|
const { page = 1, pageSize = 10, kai_piao_ri_qi, project_number, project_leader, project_name } = req.body;
|
||||||
|
const pageNum = Math.max(1, Number(page));
|
||||||
|
const size = Math.max(1, Number(pageSize));
|
||||||
|
|
||||||
|
// 如果提供了开票日期,则验证格式并查询对应的项目号
|
||||||
|
let projectIdsFromInvoice: string[] = [];
|
||||||
|
if (kai_piao_ri_qi) {
|
||||||
|
projectIdsFromInvoice = await getProjectIdsByInvoiceDate(kai_piao_ri_qi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提供了项目编号,则将其添加到筛选条件中
|
||||||
|
if (project_number) {
|
||||||
|
projectIdsFromInvoice.push(project_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目总数
|
||||||
|
const countWhere: any = {};
|
||||||
|
if (projectIdsFromInvoice.length > 0) {
|
||||||
|
countWhere.project_id = { in: projectIdsFromInvoice };
|
||||||
|
}
|
||||||
|
if (project_leader) {
|
||||||
|
countWhere.project_leader = project_leader;
|
||||||
|
}
|
||||||
|
if (project_name) {
|
||||||
|
countWhere.project_name = { contains: project_name };
|
||||||
|
}
|
||||||
|
const total = await prismaClient.project.count(Object.keys(countWhere).length > 0 ? { where: countWhere } : undefined);
|
||||||
|
|
||||||
|
// 分页查询项目
|
||||||
|
const conditions: any[] = [];
|
||||||
|
if (projectIdsFromInvoice.length > 0) {
|
||||||
|
conditions.push(Prisma.sql`project_id IN (${Prisma.join(projectIdsFromInvoice)})`);
|
||||||
|
}
|
||||||
|
if (project_leader) {
|
||||||
|
conditions.push(Prisma.sql`project_leader = ${project_leader}`);
|
||||||
|
}
|
||||||
|
if (project_name) {
|
||||||
|
conditions.push(Prisma.sql`project_name LIKE ${'%' + project_name + '%'}`);
|
||||||
|
}
|
||||||
|
let whereClause = Prisma.sql``;
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
whereClause = Prisma.sql`WHERE ${conditions[0]}`;
|
||||||
|
for (let i = 1; i < conditions.length; i++) {
|
||||||
|
whereClause = Prisma.sql`${whereClause} AND ${conditions[i]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const projects: any[] = await prismaClient.$queryRaw(Prisma.sql`
|
||||||
|
SELECT * FROM project
|
||||||
|
${whereClause}
|
||||||
|
ORDER BY CAST(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
project_id,
|
||||||
|
'A',''),'B',''),'C',''),'D',''),'E',''),'F',''),'G',''),'H',''),'I',''),'J',''),
|
||||||
|
'K',''),'L',''),'M',''),'N',''),'O',''),'P',''),'Q',''),'R',''),'S',''),'T',''),
|
||||||
|
'U',''),'V',''),'W',''),'X',''),'Y',''),'Z',''),
|
||||||
|
'a',''),'b',''),'c',''),'d',''),'e',''),'f',''),'g',''),'h',''),'i',''),'j',''),
|
||||||
|
'k',''),'l',''),'m',''),'n',''),'o',''),'p',''),'q',''),'r',''),'s',''),'t',''),
|
||||||
|
'u',''),'v',''),'w',''),'x',''),'y',''),'z','')
|
||||||
|
AS UNSIGNED) DESC
|
||||||
|
LIMIT ${size} OFFSET ${pageNum - 1 >= 0 ? (pageNum - 1) * size : 0}
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 后端已按项目编号倒序查询,无需再次排序
|
||||||
|
|
||||||
|
const excelDatas = await getBussinessDetailsList(projects);
|
||||||
|
const startIndex = (pageNum - 1) * size;
|
||||||
|
const excelDatasWithSeq = excelDatas.map((row, idx) => ({
|
||||||
|
...row,
|
||||||
|
'序号': startIndex + idx + 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 构造分页信息
|
||||||
|
const totalPages = Math.ceil(total / size);
|
||||||
|
const pagination = {
|
||||||
|
total,
|
||||||
|
page: pageNum,
|
||||||
|
pageSize: size,
|
||||||
|
totalPages
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取成功',
|
||||||
|
data: {
|
||||||
|
data: excelDatasWithSeq,
|
||||||
|
pagination
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
res.status(500).json({ message: '获取数据时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
src/pages/api/business_details/sum.ts
Normal file
81
src/pages/api/business_details/sum.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
import { getBussinessDetailsList } from '../project/util'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
totals: Record<string, string>
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const projects = await prismaClient.project.findMany()
|
||||||
|
const rows = await getBussinessDetailsList(projects)
|
||||||
|
|
||||||
|
const amountKeyMap: Record<string, string> = {
|
||||||
|
'预计总支出 (不含税)': 'estimatedExpenseExclTax',
|
||||||
|
'开票收入(不含税)': 'invoiceRevenueExclTax',
|
||||||
|
'开票金额(含税)': 'invoiceAmountInclTax',
|
||||||
|
'已回款': 'receivedPayment',
|
||||||
|
'未回款金额': 'unreceivedAmount',
|
||||||
|
'已报销': 'reimbursed',
|
||||||
|
'预计未报销金额': 'estimatedUnreimbursed',
|
||||||
|
'已支付': 'paid',
|
||||||
|
}
|
||||||
|
|
||||||
|
const totals: Record<string, BigNumber> = Object.fromEntries(
|
||||||
|
Object.values(amountKeyMap).map(k => [k, new BigNumber(0)])
|
||||||
|
)
|
||||||
|
|
||||||
|
const parseAmount = (v: any): BigNumber => {
|
||||||
|
if (v === null || v === undefined) return new BigNumber(0)
|
||||||
|
if (BigNumber.isBigNumber(v)) return v
|
||||||
|
if (typeof v === 'number') {
|
||||||
|
const bn = new BigNumber(v)
|
||||||
|
return bn.isNaN() ? new BigNumber(0) : bn
|
||||||
|
}
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const s = v.trim()
|
||||||
|
if (s === '' || s === '-') return new BigNumber(0)
|
||||||
|
const s2 = s.replace(/,/g, '')
|
||||||
|
const bn = new BigNumber(s2)
|
||||||
|
return bn.isNaN() ? new BigNumber(0) : bn
|
||||||
|
}
|
||||||
|
return new BigNumber(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
for (const [cnKey, enKey] of Object.entries(amountKeyMap)) {
|
||||||
|
const num = parseAmount(row[cnKey])
|
||||||
|
totals[enKey] = totals[enKey].plus(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted: Record<string, string> = Object.fromEntries(
|
||||||
|
Object.entries(totals).map(([k, v]) => [k, v.toString()])
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取成功',
|
||||||
|
data: {
|
||||||
|
totals: formatted,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('business_details/sum handler:', error)
|
||||||
|
res.status(500).json({
|
||||||
|
code: 500,
|
||||||
|
message: '获取数据时出错',
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/pages/api/business_details/util.ts
Normal file
133
src/pages/api/business_details/util.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { prismaClient } from '@/lib/prisma';
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据开票日期获取对应的项目号
|
||||||
|
* @param kai_piao_ri_qi 开票日期,格式为 YYYY-MM-DD
|
||||||
|
* @returns 匹配的项目号数组
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 根据开票日期或日期范围获取对应的项目号
|
||||||
|
* 支持:
|
||||||
|
* - 单个日期字符串:'YYYY-MM-DD'
|
||||||
|
* - JSON数组字符串:'["2025-09-01","2025-09-30",""]'
|
||||||
|
* - 直接数组:['2025-09-01','2025-09-30']
|
||||||
|
*/
|
||||||
|
export async function getProjectIdsByInvoiceDate(kai_piao_ri_qi: unknown): Promise<string[]> {
|
||||||
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
|
|
||||||
|
const normalizeDate = (input: unknown): string | undefined => {
|
||||||
|
if (input === null || input === undefined) return undefined;
|
||||||
|
if (typeof input === 'number' && Number.isFinite(input)) {
|
||||||
|
const parsed = dayjs(input);
|
||||||
|
return parsed.isValid() ? parsed.format('YYYY-MM-DD') : undefined;
|
||||||
|
}
|
||||||
|
const s = String(input).trim();
|
||||||
|
if (!s) return undefined;
|
||||||
|
if (dateRegex.test(s)) return s;
|
||||||
|
const parsed = dayjs(s);
|
||||||
|
if (parsed.isValid()) return parsed.format('YYYY-MM-DD');
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
let start: string | undefined;
|
||||||
|
let end: string | undefined;
|
||||||
|
|
||||||
|
if (Array.isArray(kai_piao_ri_qi)) {
|
||||||
|
start = normalizeDate(kai_piao_ri_qi[0]);
|
||||||
|
end = normalizeDate(kai_piao_ri_qi[1]);
|
||||||
|
} else if (kai_piao_ri_qi && typeof kai_piao_ri_qi === 'object') {
|
||||||
|
const obj = kai_piao_ri_qi as any;
|
||||||
|
start = normalizeDate(obj?.start ?? obj?.startTime ?? obj?.begin ?? obj?.beginTime ?? obj?.[0]);
|
||||||
|
end = normalizeDate(obj?.end ?? obj?.endTime ?? obj?.finish ?? obj?.finishTime ?? obj?.[1]);
|
||||||
|
} else if (typeof kai_piao_ri_qi === 'string') {
|
||||||
|
const raw = kai_piao_ri_qi.trim();
|
||||||
|
if (dateRegex.test(raw)) {
|
||||||
|
start = normalizeDate(raw);
|
||||||
|
} else if (raw.startsWith('[')) {
|
||||||
|
// 尝试解析 JSON 数组字符串,例如 '["2025-09-03","2025-09-05",""]'
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(raw);
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
start = normalizeDate(arr[0]);
|
||||||
|
end = normalizeDate(arr[1]);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 非法 JSON,后续会走错误提示
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const parts = raw.split(/[,,、\s]+/).map(s => s.trim()).filter(Boolean);
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
start = normalizeDate(parts[0]);
|
||||||
|
end = normalizeDate(parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装 where 条件:区间或单点
|
||||||
|
// 由于数据库中日期可能以 YYYY-MM-DD 或 YYYY/MM/DD 存储,需要同时支持两种格式
|
||||||
|
let whereClause: any;
|
||||||
|
|
||||||
|
if (start && end) {
|
||||||
|
const startDash = start.replace(/\//g, '-');
|
||||||
|
const endExclusiveDash = dayjs(end).add(1, 'day').format('YYYY-MM-DD');
|
||||||
|
const startSlash = start.replace(/-/g, '/');
|
||||||
|
const endExclusiveSlash = dayjs(end).add(1, 'day').format('YYYY/MM/DD');
|
||||||
|
|
||||||
|
whereClause = {
|
||||||
|
OR: [
|
||||||
|
{ kai_piao_ri_qi: { gte: startDash, lt: endExclusiveDash } },
|
||||||
|
{ kai_piao_ri_qi: { gte: startSlash, lt: endExclusiveSlash } }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
} else if (start) {
|
||||||
|
const startDash = start.replace(/\//g, '-');
|
||||||
|
const startSlash = start.replace(/-/g, '/');
|
||||||
|
whereClause = {
|
||||||
|
OR: [
|
||||||
|
{ kai_piao_ri_qi: { startsWith: startDash } },
|
||||||
|
{ kai_piao_ri_qi: { startsWith: startSlash } }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error('开票日期格式不正确,应为 YYYY-MM-DD 或 ["YYYY-MM-DD","YYYY-MM-DD",""]');
|
||||||
|
}
|
||||||
|
|
||||||
|
const voidRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: {
|
||||||
|
remark: {
|
||||||
|
in: ['发票作废', '作废']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: { dataId: true }
|
||||||
|
});
|
||||||
|
const voidIds = voidRecords.map(r => r.dataId).filter(Boolean);
|
||||||
|
|
||||||
|
const invoiceRows = await prismaClient.invoice_data.findMany({
|
||||||
|
where: {
|
||||||
|
...whereClause
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
dataid: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const dataIdsRaw = Array.from(new Set(invoiceRows.map(r => String(r.dataid ?? '').trim()).filter(Boolean)));
|
||||||
|
const voidIdSet = new Set(voidIds.map(id => String(id).trim()).filter(Boolean));
|
||||||
|
const dataIds = voidIdSet.size > 0 ? dataIdsRaw.filter(id => !voidIdSet.has(id)) : dataIdsRaw;
|
||||||
|
if (dataIds.length === 0) return [];
|
||||||
|
|
||||||
|
const apps = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: {
|
||||||
|
dataId: { in: dataIds }
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
projectNumber: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const projectIds = Array.from(new Set(
|
||||||
|
apps.flatMap(item => ((item.projectNumber || '') as string)
|
||||||
|
.split(/[,,、\s]+/)
|
||||||
|
.filter(Boolean))
|
||||||
|
));
|
||||||
|
return projectIds;
|
||||||
|
}
|
||||||
140
src/pages/api/dingding/backfillApprovalNumber.ts
Normal file
140
src/pages/api/dingding/backfillApprovalNumber.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import AuthClient from '@/api/dingding/auth'
|
||||||
|
import OAClient from '@/api/dingding/oa'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
import pLimit from 'p-limit'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
totalTargets: number
|
||||||
|
updated: number
|
||||||
|
skipped: number
|
||||||
|
errors: Array<{ dataId: string; error: string }>
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ code: 405, message: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
const body = req.body || {}
|
||||||
|
const inputIds: string[] = Array.isArray(body.dataIds) ? body.dataIds.filter(Boolean) : []
|
||||||
|
const limitNum = Number(body.limit || 200)
|
||||||
|
const concurrency = Math.max(1, Number(body.concurrency || 3))
|
||||||
|
const qps = Math.min(35, Math.max(1, Number(body.qps || 10)))
|
||||||
|
const whereClause = inputIds.length > 0
|
||||||
|
? { dataId: { in: inputIds } }
|
||||||
|
: { approvalNumber: null }
|
||||||
|
const targets = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
select: { dataId: true },
|
||||||
|
take: limitNum
|
||||||
|
})
|
||||||
|
const ids = Array.from(new Set(targets.map(t => t.dataId).filter(Boolean)))
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: 'No records to backfill',
|
||||||
|
data: { totalTargets: 0, updated: 0, skipped: 0, errors: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const accessToken = await AuthClient.getAccessToken()
|
||||||
|
const limit = pLimit(concurrency)
|
||||||
|
let updated = 0
|
||||||
|
let skipped = 0
|
||||||
|
const errors: Array<{ dataId: string; error: string }> = []
|
||||||
|
|
||||||
|
const window: number[] = []
|
||||||
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
const acquire = async () => {
|
||||||
|
const now = Date.now()
|
||||||
|
while (window.length > 0 && now - window[0] > 1000) {
|
||||||
|
window.shift()
|
||||||
|
}
|
||||||
|
if (window.length >= qps) {
|
||||||
|
const waitMs = 1000 - (now - window[0]) + 5
|
||||||
|
await sleep(waitMs)
|
||||||
|
}
|
||||||
|
window.push(Date.now())
|
||||||
|
}
|
||||||
|
const getBusinessIdWithRetry = async (dataId: string) => {
|
||||||
|
let attempt = 0
|
||||||
|
const maxAttempts = 5
|
||||||
|
while (attempt < maxAttempts) {
|
||||||
|
try {
|
||||||
|
await acquire()
|
||||||
|
const resp = await OAClient.getProcessInstanceWithOptions({
|
||||||
|
processInstanceId: dataId,
|
||||||
|
dingtalkAccessToken: accessToken
|
||||||
|
})
|
||||||
|
if (!resp) {
|
||||||
|
throw { message: 'processInstanceIdError', code: 'processInstanceIdError' }
|
||||||
|
}
|
||||||
|
const businessId: string = get(resp, 'body.result.businessId', '') || ''
|
||||||
|
if (businessId) return businessId
|
||||||
|
return ''
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg: string = e?.message || ''
|
||||||
|
const code: string = e?.code || ''
|
||||||
|
if (code === 'Forbidden.AccessDenied.QpsLimitForAppkeyAndApi' || /QpsLimitForAppkeyAndApi/i.test(msg)) {
|
||||||
|
await sleep(1200 + attempt * 400)
|
||||||
|
attempt++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidId = (id: string) => {
|
||||||
|
if (!id || typeof id !== 'string') return false
|
||||||
|
const s = id.trim()
|
||||||
|
if (!s) return false
|
||||||
|
if (s.length < 10 || s.length > 128) return false
|
||||||
|
return /^[A-Za-z0-9\-\_]+$/.test(s)
|
||||||
|
}
|
||||||
|
const tasks = ids.map(id => limit(async () => {
|
||||||
|
try {
|
||||||
|
if (!isValidId(id)) {
|
||||||
|
skipped += 1
|
||||||
|
errors.push({ dataId: id, error: 'processInstanceIdError' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const businessId = await getBusinessIdWithRetry(id)
|
||||||
|
if (!businessId) {
|
||||||
|
skipped += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await prismaClient.dingding_oa_invoice_application.updateMany({
|
||||||
|
where: { dataId: id },
|
||||||
|
data: { approvalNumber: businessId }
|
||||||
|
})
|
||||||
|
updated += 1
|
||||||
|
} catch (e: any) {
|
||||||
|
errors.push({ dataId: id, error: e?.message || 'unknown error' })
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
await Promise.all(tasks)
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: 'Backfill completed',
|
||||||
|
data: {
|
||||||
|
totalTargets: ids.length,
|
||||||
|
updated,
|
||||||
|
skipped,
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).json({ code: 500, message: 'Internal server error', error })
|
||||||
|
}
|
||||||
|
}
|
||||||
219
src/pages/api/dingding/config.ts
Normal file
219
src/pages/api/dingding/config.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
interface ImportConfig {
|
||||||
|
comment?: string;
|
||||||
|
tableName: string; // Prisma模型名称
|
||||||
|
fieldMapping: { [excelKey: string]: string }; // Excel列名到数据库字段的映射
|
||||||
|
idField: string; // ID字段名
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 支持的表配置
|
||||||
|
export const TABLE_CONFIGS: { [key: string]: ImportConfig } = {
|
||||||
|
project: {
|
||||||
|
tableName: 'project',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'项目部门': 'project_department',
|
||||||
|
'项目名称': 'project_name',
|
||||||
|
'客户单位名称': 'client_name',
|
||||||
|
'是否多家客户单位': 'is_multi_client',
|
||||||
|
'客户单位名称(多)': 'multi_client_name',
|
||||||
|
'项目主担': 'project_leader',
|
||||||
|
'组别缩写': 'group_abbreviation',
|
||||||
|
'项目编号': 'project_id',
|
||||||
|
'创建时间': 'create_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
procurement_contract_list: {
|
||||||
|
tableName: 'procurement_contract_list',
|
||||||
|
comment: '采购合同列表',
|
||||||
|
fieldMapping: {
|
||||||
|
'发起人': 'initiator',
|
||||||
|
'合同编号': 'contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'履约发起部门': 'lvyue_faqi_bumen',
|
||||||
|
'经办人': 'operator',
|
||||||
|
'相对方': 'xiangduifang',
|
||||||
|
'是否开口': 'is_open',
|
||||||
|
'合同总额': 'contract_amount',
|
||||||
|
'备注': 'remarks',
|
||||||
|
'履约开始时间': 'performance_start_time',
|
||||||
|
'合同原件扫描件(盖章版)': 'hetong_yuanjian_saomiaojian_gaizhangban',
|
||||||
|
'钉钉采购审批单': 'dingding_purchase_approval_file',
|
||||||
|
'合同附件扫描件': 'contract_attachment_scan',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed',
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
purchase_contract_order_management: {
|
||||||
|
comment: '采购合同订单',
|
||||||
|
tableName: 'purchase_contract_order_management',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'采购合同编号': 'purchase_contract_number',
|
||||||
|
'采购用途': 'purchase_purpose',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'项目名称': 'project_name',
|
||||||
|
'关联销售合同编号': 'associated_sales_contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
// '采购审批单号(请先填写合同编号)': 'purchase_approval_number',
|
||||||
|
'采购订单编号(请先填写合同编号)': 'purchase_approval_number',
|
||||||
|
// '合同名称(***请再次确认合同名称***)': 'purchase_contract_name',
|
||||||
|
'采购合同名称(***请再次确认合同名称***)': 'purchase_contract_name',
|
||||||
|
'相对方': 'xiang_dui_fang',
|
||||||
|
'合同类别(是否开口)': 'he_tong_lei_bie',
|
||||||
|
'合同总金额(含税)': 'he_tong_zong_jin_e_HanShui',
|
||||||
|
'剩余金额(含税)': 'sheng_yu_jin_e_HanShui',
|
||||||
|
'合同剩余金额提醒': 'he_tong_sheng_yu_jin_e_ti_xing',
|
||||||
|
'本次订单名称': 'ben_ci_ding_dan_ming_cheng',
|
||||||
|
'本次订单金额(含税)': 'ben_ci_ding_dan_jin_e_HanShui',
|
||||||
|
'本次订单履约开始时间': 'ben_ci_ding_dan_lu_yue_kai_shi_shi_jian',
|
||||||
|
'本次订单履约结束时间': 'ben_ci_ding_dan_lu_yue_jie_shu_shi_jian',
|
||||||
|
'本次订单发起部门': 'ben_ci_ding_dan_fa_qi_bu_men',
|
||||||
|
'本次订单发起人': 'ben_ci_ding_dan_fa_qi_ren',
|
||||||
|
'本次订单发起时间': 'ben_ci_ding_dan_fa_qi_shi_jian',
|
||||||
|
'本次订单文本(多文件请压缩后上传)': 'ben_ci_ding_dan_wen_ben',
|
||||||
|
'本次订单关联合同附件': 'ben_ci_ding_dan_guan_lian_he_tong_fujian',
|
||||||
|
'审批编号': 'approval_number',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'current_responsible_person',
|
||||||
|
'审批结果': 'approval_result',
|
||||||
|
'审批状态': 'approval_status',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed'
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
purchase_contract_execution_maintenance: {
|
||||||
|
comment: '销售合同',
|
||||||
|
tableName: 'purchase_contract_execution_maintenance',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'发起人': 'initiator',
|
||||||
|
'合同编号': 'contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'履约发起部门': 'lvyue_faqi_bumen',
|
||||||
|
'经办人': 'operator',
|
||||||
|
'相对方': 'xiangduifang',
|
||||||
|
'是否开口': 'is_open',
|
||||||
|
'合同总额': 'contract_amount',
|
||||||
|
'备注': 'remarks',
|
||||||
|
'履约开始时间': 'performance_start_time',
|
||||||
|
'合同原件扫描件(盖章版)': 'hetong_yuanjian_saomiaojian_gaizhangban',
|
||||||
|
'合同附件扫描件': 'contract_attachment_scan',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
expense_reimbursement: { // 费用报销
|
||||||
|
tableName: 'expense_reimbursement',
|
||||||
|
fieldMapping: {
|
||||||
|
'序号': 'xuhao',
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'项目号': 'project_number',
|
||||||
|
'日期': 'date',
|
||||||
|
'凭证号': 'voucher_number',
|
||||||
|
'分录号': 'entry_number',
|
||||||
|
'摘要': 'summary',
|
||||||
|
'科目编码': 'account_code',
|
||||||
|
'科目名称': 'account_name',
|
||||||
|
'辅助项': 'auxiliary_items',
|
||||||
|
'币种': 'currency',
|
||||||
|
'借方原币': 'debit_original_currency',
|
||||||
|
'借方本币': 'debit_local_currency',
|
||||||
|
'贷方原币': 'credit_original_currency',
|
||||||
|
'贷方本币': 'credit_local_currency',
|
||||||
|
'SR号': 'sr_number',
|
||||||
|
'共享凭证号': 'shared_voucher_number',
|
||||||
|
'费用预算项目': 'expense_budget_item',
|
||||||
|
'成本中心': 'cost_center',
|
||||||
|
'标准名称': 'standard_name',
|
||||||
|
'一级费用类型': 'expense_type_level1',
|
||||||
|
'二级费用类型': 'expense_type_level2',
|
||||||
|
'三级费用类型': 'expense_type_level3',
|
||||||
|
'金额': 'amount',
|
||||||
|
'创建时间': 'created_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'updated_time'
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
actual_payment: { // 实际支付
|
||||||
|
tableName: 'actual_payment',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'支付日期': 'payment_date',
|
||||||
|
'年份': 'payment_year',
|
||||||
|
'凭证号': 'pingzhenghao',
|
||||||
|
'行项目文本': 'hangxiangmu_wenben',
|
||||||
|
'业务线': 'business_line',
|
||||||
|
"期末余额(元)": 'qimo_yueyuan',
|
||||||
|
'期末余额(元)(大写)': 'qimo_yueyuan_daxie',
|
||||||
|
'支付方式': 'payment_method',
|
||||||
|
'供应商名称': 'gongyingshang_mingcheng',
|
||||||
|
'创建时间': 'created_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'updated_time'
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
// 客户管理
|
||||||
|
customer_management_info: {
|
||||||
|
tableName: 'customer_management_info',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'客户单位名称': 'customer_unit_name',
|
||||||
|
'客户分类': 'customer_type',
|
||||||
|
},
|
||||||
|
idField: 'data_id'
|
||||||
|
},
|
||||||
|
invoice_void_record: {
|
||||||
|
comment: '发票作废记录表',
|
||||||
|
tableName: 'invoice_void_record',
|
||||||
|
fieldMapping: {
|
||||||
|
'数据id': 'dataId',
|
||||||
|
'所在部门': 'applicationDepartment',
|
||||||
|
'申请业务组': 'xiang_mu_zu_bie',
|
||||||
|
'申请日期': 'applicationDate',
|
||||||
|
'申请人': 'applicant',
|
||||||
|
'发票号码': 'piao_hao',
|
||||||
|
'开票日期': 'kai_piao_ri_qi',
|
||||||
|
'协议类型': 'agreementType',
|
||||||
|
'作废事由': 'zuo_fei_shi_you',
|
||||||
|
'作废原因': 'cancelReason',
|
||||||
|
'发票金额': 'invoice_amount_yuan',
|
||||||
|
'金额是否含税': 'is_tax_inclusive',
|
||||||
|
'费用所属时间': 'feeBelongingTime',
|
||||||
|
'付款单位名称': 'customerCompanyName',
|
||||||
|
'付款单位纳税人识别号': 'tong_yi_she_hui_xin_yong_dai_ma',
|
||||||
|
'付款单位开户行': 'bankName',
|
||||||
|
'付款单位开户账号': 'bankAccount',
|
||||||
|
'上传作废发票附件': 'uploadedVoidInvoiceAttachment',
|
||||||
|
'手写签名附件': 'handwrittenSignatureAttachment',
|
||||||
|
'项目编号': 'projectNumber',
|
||||||
|
'备注信息': 'remark'
|
||||||
|
},
|
||||||
|
idField: 'dataId'
|
||||||
|
}
|
||||||
|
};
|
||||||
99
src/pages/api/dingding/corn.ts
Normal file
99
src/pages/api/dingding/corn.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
import { logger } from '@/api/winston/logger';
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
message: string
|
||||||
|
testjsonres?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 定义请求配置接口
|
||||||
|
interface ImportRequestConfig {
|
||||||
|
tableName: string;
|
||||||
|
processCode: string;
|
||||||
|
daysFromNow: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用的importData接口调用函数
|
||||||
|
async function callImportDataAPI(baseUrl: string, config: ImportRequestConfig) {
|
||||||
|
const url = new URL('/api/dingding/importData', baseUrl);
|
||||||
|
url.searchParams.append('tableName', config.tableName);
|
||||||
|
url.searchParams.append('PROCESS_CODE', config.processCode);
|
||||||
|
url.searchParams.append('daysFromNow', config.daysFromNow);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 通用的importData接口调用函数
|
||||||
|
async function callImportInvoiceApprovalDataAPI(baseUrl: string, config: ImportRequestConfig) {
|
||||||
|
const url = new URL('/api/dingding/importInvoiceApprovalData', baseUrl);
|
||||||
|
url.searchParams.append('tableName', config.tableName);
|
||||||
|
url.searchParams.append('PROCESS_CODE', config.processCode);
|
||||||
|
url.searchParams.append('daysFromNow', config.daysFromNow);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
logger.info('调用corn接口')
|
||||||
|
// 使用 req.headers.host 获取主机域名
|
||||||
|
const host = req.headers.host || 'localhost:3000';
|
||||||
|
const protocol = req.headers.referer?.startsWith('https') ? 'https' : 'http';
|
||||||
|
const baseUrl = `${protocol}://${host}`;
|
||||||
|
|
||||||
|
// // 定义请求配置
|
||||||
|
// const requests: ImportRequestConfig[] = [
|
||||||
|
// {
|
||||||
|
// tableName: 'purchase_contract_execution_maintenance',
|
||||||
|
// processCode: 'PROC-870CF43E-EF32-4BCF-BC4B-D78FD12DADC4',
|
||||||
|
// daysFromNow: '3'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// tableName: 'purchase_contract_order_management',
|
||||||
|
// processCode: 'PROC-4FB03ECE-6F80-413F-90C3-CC9DCA903A2F',
|
||||||
|
// daysFromNow: '3'
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
// // 执行所有请求
|
||||||
|
// const results = [];
|
||||||
|
// for (const config of requests) {
|
||||||
|
// const result = await callImportDataAPI(baseUrl, config);
|
||||||
|
// results.push(result);
|
||||||
|
// }
|
||||||
|
await callImportInvoiceApprovalDataAPI(baseUrl, {
|
||||||
|
tableName: 'dingding_oa_invoice_application',
|
||||||
|
processCode: 'PROC-C8B8B69F-2F2C-4B76-9831-0E68C89A367F',
|
||||||
|
daysFromNow: '15'
|
||||||
|
})
|
||||||
|
logger.info('调用corn接口-结束')
|
||||||
|
res.status(200).json({
|
||||||
|
message: `成功调用importData接口`,
|
||||||
|
testjsonres: JSON.stringify(['results'])
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调用importData接口时出错:', error);
|
||||||
|
res.status(500).json({ message: `调用importData接口时出错: ${error.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/pages/api/dingding/genericImport.ts
Normal file
103
src/pages/api/dingding/genericImport.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import prismaClient from '@/lib/prisma';
|
||||||
|
import { TABLE_CONFIGS } from './config'
|
||||||
|
// 定义通用导入配置接口
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从查询参数获取表名
|
||||||
|
const tableName = req?.fields?.tableName as string ?? '';
|
||||||
|
|
||||||
|
// 验证表名
|
||||||
|
if (!tableName || !TABLE_CONFIGS[tableName]) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Invalid table name',
|
||||||
|
supportedTables: Object.keys(TABLE_CONFIGS)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableConfig = TABLE_CONFIGS[tableName];
|
||||||
|
|
||||||
|
// 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer();
|
||||||
|
if (!fileBuffer) {
|
||||||
|
return res.status(400).json({ error: 'File not provided' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: any[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
|
||||||
|
// 数据处理
|
||||||
|
const dataList: any[] = [];
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const data: any = {};
|
||||||
|
|
||||||
|
// 设置ID字段
|
||||||
|
data[tableConfig.idField] = rawItem['数据id'];
|
||||||
|
|
||||||
|
// 映射字段
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(tableConfig.fieldMapping)) {
|
||||||
|
data[dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
dataList.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询数据库中已存在的记录
|
||||||
|
const existingRecords = await prismaClient[tableConfig.tableName].findMany({
|
||||||
|
where: {
|
||||||
|
[tableConfig.idField]: {
|
||||||
|
in: dataList.map(item => item[tableConfig.idField])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
[tableConfig.idField]: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const existingIdSet = new Set(existingRecords.map((record: any) => record[tableConfig.idField]));
|
||||||
|
const newDataList = dataList.filter(item => !existingIdSet.has(item[tableConfig.idField]));
|
||||||
|
// 如果没有新数据需要插入,则直接返回
|
||||||
|
if (newDataList.length === 0) {
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: `${tableName}数据导入完成,无新数据需要插入`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Prisma动态调用对应模型的createMany方法
|
||||||
|
await prismaClient[tableConfig.tableName].createMany({
|
||||||
|
data: newDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: `${tableName}数据导入成功,共插入${newDataList.length}条新数据`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Generic import error:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error });
|
||||||
|
}
|
||||||
|
});
|
||||||
166
src/pages/api/dingding/importData.ts
Normal file
166
src/pages/api/dingding/importData.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* 技术方案:
|
||||||
|
* https://docs.dingtalk.com/i/nodes/Obva6QBXJw6YEvDyila56vb0Vn4qY5Pr
|
||||||
|
* 我现在想要搞得是自动化将钉钉oa的数据读取到mysql中的数据
|
||||||
|
* 配置项目1 code
|
||||||
|
* 配置项目2 表格名字
|
||||||
|
* 配置项目3 字段映射
|
||||||
|
* 1. 根据审批code 从接口中获得实例id列表
|
||||||
|
* 2. 根据实例id列表请求对应的实例
|
||||||
|
* 3. 将实例id根据表格映射拼接成对应的数据
|
||||||
|
* 4. 然后根据对应的表格和映射关系将其录入到数据库中
|
||||||
|
* 5. 完成操作
|
||||||
|
*/
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
import AuthClient from '@/api/dingding/auth'
|
||||||
|
import OAClient from '@/api/dingding/oa'
|
||||||
|
import { FIELD_MAPPINGS, DATA_ID_FIELD_MAPPINGS } from '@/CONSTANTS'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { logger } from '@/api/winston/logger'
|
||||||
|
import { getInstanceIdList } from '@/pages/api/dingding/util'
|
||||||
|
import pLimit from 'p-limit'
|
||||||
|
const DEFAULT_PROCESS_CODE = 'PROC-870CF43E-EF32-4BCF-BC4B-D78FD12DADC4' //process.env.DINGDING_INVOICE_APPROVAL_PROCESS_CODE ?? ''
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
newInstanceIds?: string[]
|
||||||
|
error?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const tableName = req.query.tableName as string ?? 'purchase_contract_execution_maintenance'
|
||||||
|
const PROCESS_CODE = req.query.processCode as string ?? DEFAULT_PROCESS_CODE
|
||||||
|
|
||||||
|
// 获取时间参数,支持 startTime 和 endTime 字符串格式
|
||||||
|
const startTimeStr = req.query.startTime as string
|
||||||
|
const endTimeStr = req.query.endTime as string
|
||||||
|
const daysFromNow = Number(req.query.daysFromNow) ?? 3;
|
||||||
|
|
||||||
|
const accessToken = await AuthClient.getAccessToken();
|
||||||
|
console.log("获得token", accessToken);
|
||||||
|
|
||||||
|
// 处理时间逻辑:优先使用传入的 startTime 和 endTime,否则使用 daysFromNow
|
||||||
|
let startTime: number
|
||||||
|
let endTime: number | undefined
|
||||||
|
|
||||||
|
if (startTimeStr) {
|
||||||
|
// 如果传入了 startTime 字符串,转换为时间戳
|
||||||
|
startTime = dayjs(startTimeStr).valueOf()
|
||||||
|
} else {
|
||||||
|
// 否则使用 daysFromNow 计算开始时间
|
||||||
|
startTime = dayjs().subtract(daysFromNow, 'day').valueOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTimeStr) {
|
||||||
|
// 如果传入了 endTime 字符串,转换为时间戳
|
||||||
|
endTime = dayjs(endTimeStr).valueOf()
|
||||||
|
} else {
|
||||||
|
// 否则不设置结束时间
|
||||||
|
endTime = undefined
|
||||||
|
}
|
||||||
|
// https://open.dingtalk.com/document/orgapp/obtain-an-approval-list-of-instance-ids
|
||||||
|
const instanceIdList = await getInstanceIdList({
|
||||||
|
accessToken: accessToken,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
maxResults: 20,
|
||||||
|
processCode: PROCESS_CODE
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataIdField = DATA_ID_FIELD_MAPPINGS[tableName]
|
||||||
|
const existingData = await prismaClient[tableName].findMany({
|
||||||
|
select: {
|
||||||
|
[dataIdField]: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 提取已存在的data_id
|
||||||
|
const existingDataIds = existingData.map(item => item[dataIdField]);
|
||||||
|
// 计算差集,只处理不存在于数据库中的instanceId
|
||||||
|
const newInstanceIds = instanceIdList.filter(id => !existingDataIds.includes(id));
|
||||||
|
if (newInstanceIds.length > 0) {
|
||||||
|
// 创建一个限制并发数为5的限制器
|
||||||
|
const limit = pLimit(20);
|
||||||
|
|
||||||
|
// 使用 p-limit 来限制并发请求数,并在每个请求完成后等待1秒
|
||||||
|
const promises = newInstanceIds.map(instanceId =>
|
||||||
|
limit(async () => {
|
||||||
|
const result = await OAClient.getProcessInstanceWithOptions({
|
||||||
|
processInstanceId: instanceId,
|
||||||
|
dingtalkAccessToken: accessToken
|
||||||
|
});
|
||||||
|
// 请求完成后等待1秒
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 使用 Promise.allSettled 等待所有结果完成
|
||||||
|
const results = await Promise.allSettled(promises);
|
||||||
|
// 汇总成功的结果
|
||||||
|
const successfulResults = results
|
||||||
|
.filter(result => result.status === 'fulfilled')
|
||||||
|
.map(result => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = results
|
||||||
|
.filter(result => result.status === 'rejected')
|
||||||
|
.map(result => (result as PromiseRejectedResult).reason);
|
||||||
|
|
||||||
|
if (failedResults.length > 0) {
|
||||||
|
console.error('有部分请求失败:', failedResults);
|
||||||
|
logger.error('有部分请求失败:', { failedResults })
|
||||||
|
}
|
||||||
|
const mappedDatas = successfulResults.map(item => {
|
||||||
|
const formComponentValues = get(item, 'body.result.formComponentValues', []);
|
||||||
|
const processInstanceId = get(item, 'processInstanceId', '');
|
||||||
|
const mappedData: any = {};
|
||||||
|
formComponentValues.forEach(fv => {
|
||||||
|
const fieldName = fv.name;
|
||||||
|
const fieldValue = fv.value;
|
||||||
|
|
||||||
|
// 检查是否有对应的字段映射
|
||||||
|
const tablekey = FIELD_MAPPINGS[tableName][fieldName]
|
||||||
|
if (tablekey) {
|
||||||
|
mappedData[tablekey] = fieldValue ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
if (processInstanceId) {
|
||||||
|
mappedData.data_id = processInstanceId;
|
||||||
|
}
|
||||||
|
if (tableName === 'purchase_contract_order_management') {
|
||||||
|
mappedData['approval_result'] = '审批通过';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...mappedData,
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log("---")
|
||||||
|
try {
|
||||||
|
await prismaClient[tableName].createMany({
|
||||||
|
data: mappedDatas,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('插入数据时出错:', error);
|
||||||
|
logger.error('插入数据时出错:', { error })
|
||||||
|
res.status(400).json({ code: 400, message: `数据插入到${tableName}表中失败`, error: error, })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info(`数据已成功插入到${tableName}表中; count: ${newInstanceIds.length}; ${JSON.stringify(newInstanceIds)}`,)
|
||||||
|
res.status(200).json({ code: 0, message: `数据已成功插入到${tableName}表中`, newInstanceIds })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
logger.error('获取 token 时出错:', { error })
|
||||||
|
res.status(500).json({ code: 500, message: '获取 token 时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
823
src/pages/api/dingding/importInvoiceApprovalData.ts
Normal file
823
src/pages/api/dingding/importInvoiceApprovalData.ts
Normal file
@@ -0,0 +1,823 @@
|
|||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* 技术方案:
|
||||||
|
* https://docs.dingtalk.com/i/nodes/Obva6QBXJw6YEvDyila56vb0Vn4qY5Pr
|
||||||
|
* 我现在想要搞得是自动化将钉钉oa的数据读取到mysql中的数据
|
||||||
|
* 配置项目1 code
|
||||||
|
* 配置项目2 表格名字
|
||||||
|
* 配置项目3 字段映射
|
||||||
|
* 1. 根据审批code 从接口中获得实例id列表
|
||||||
|
* 2. 根据实例id列表请求对应的实例
|
||||||
|
* 3. 将实例id根据表格映射拼接成对应的数据
|
||||||
|
* 4. 然后根据对应的表格和映射关系将其录入到数据库中
|
||||||
|
* 5. 完成操作
|
||||||
|
*/
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
import AuthClient from '@/api/dingding/auth'
|
||||||
|
import OAClient from '@/api/dingding/oa'
|
||||||
|
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { logger } from '@/api/winston/logger'
|
||||||
|
import { getInstanceIdList } from '@/pages/api/dingding/util'
|
||||||
|
import {
|
||||||
|
FIELD_MAPPINGS,
|
||||||
|
DATA_ID_FIELD_MAPPINGS,
|
||||||
|
AGREEMENT_TYPE_CODE,
|
||||||
|
AGREEMENT_TYPE_NAME,
|
||||||
|
INVOICE_CODE
|
||||||
|
} from '@/CONSTANTS';
|
||||||
|
import { InvoiceApplicationAmountDetails } from '../type'
|
||||||
|
import pLimit from 'p-limit'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_PROCESS_CODE = 'PROC-C8B8B69F-2F2C-4B76-9831-0E68C89A367F' //process.env.DINGDING_INVOICE_APPROVAL_PROCESS_CODE ?? ''
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
newInstanceIds?: string[]
|
||||||
|
error?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const tableName = req.query.tableName as string ?? 'purchase_contract_execution_maintenance'
|
||||||
|
const PROCESS_CODE = req.query.processCode as string ?? DEFAULT_PROCESS_CODE
|
||||||
|
|
||||||
|
// 获取时间参数,支持 startTime 和 endTime 字符串格式
|
||||||
|
const startTimeStr = req.query.startTime as string
|
||||||
|
const endTimeStr = req.query.endTime as string
|
||||||
|
logger.info('importInvoiceApprovalData params', { tableName, processCode: PROCESS_CODE, startTimeStr, endTimeStr });
|
||||||
|
const accessToken = await AuthClient.getAccessToken();
|
||||||
|
console.log("获得token", accessToken);
|
||||||
|
|
||||||
|
// 处理时间逻辑:优先使用传入的 startTime 和 endTime,否则使用 daysFromNow
|
||||||
|
let startTime: number
|
||||||
|
let endTime: number | undefined
|
||||||
|
|
||||||
|
if (startTimeStr) {
|
||||||
|
startTime = dayjs(startTimeStr).valueOf()
|
||||||
|
} else {
|
||||||
|
startTime = dayjs().subtract(60, 'day').valueOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTimeStr) {
|
||||||
|
endTime = dayjs(endTimeStr).valueOf()
|
||||||
|
} else {
|
||||||
|
endTime = Date.now()
|
||||||
|
}
|
||||||
|
logger.info('importInvoiceApprovalData timeRange', { startTime, endTime });
|
||||||
|
|
||||||
|
const instanceIdList = await getInstanceIdList({
|
||||||
|
accessToken: accessToken,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
maxResults: 20,
|
||||||
|
processCode: PROCESS_CODE
|
||||||
|
})
|
||||||
|
logger.info('importInvoiceApprovalData instanceIdList', { count: instanceIdList.length, sample: instanceIdList.slice(0, 5) })
|
||||||
|
|
||||||
|
const dataIdField = DATA_ID_FIELD_MAPPINGS[tableName]
|
||||||
|
const existingData = await prismaClient[tableName].findMany({
|
||||||
|
select: {
|
||||||
|
[dataIdField]: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 提取已存在的data_id
|
||||||
|
const existingDataIds = existingData.map(item => item[dataIdField]);
|
||||||
|
// 计算差集,只处理不存在于数据库中的instanceId
|
||||||
|
const newInstanceIds = instanceIdList.filter(id => !existingDataIds.includes(id));
|
||||||
|
logger.info('importInvoiceApprovalData newInstanceIds', { totalFetched: instanceIdList.length, existed: existingDataIds.length, toFetch: newInstanceIds.length, sample: newInstanceIds.slice(0, 5) })
|
||||||
|
if (newInstanceIds.length > 0) {
|
||||||
|
// 创建一个限制并发数为5的限制器
|
||||||
|
const limit = pLimit(20);
|
||||||
|
// 使用 p-limit 来限制并发请求数,并在每个请求完成后等待1秒
|
||||||
|
const promises = newInstanceIds.map(instanceId =>
|
||||||
|
limit(async () => {
|
||||||
|
const result = await OAClient.getProcessInstanceWithOptions({
|
||||||
|
processInstanceId: instanceId,
|
||||||
|
dingtalkAccessToken: accessToken
|
||||||
|
});
|
||||||
|
// 请求完成后等待1秒
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// 使用 Promise.allSettled 等待所有结果完成
|
||||||
|
const results = await Promise.allSettled(promises);
|
||||||
|
const successfulResults = results
|
||||||
|
.filter(result => result.status === 'fulfilled')
|
||||||
|
.map(result => (result as PromiseFulfilledResult<any>).value);
|
||||||
|
|
||||||
|
const failedResults = results
|
||||||
|
.filter(result => result.status === 'rejected')
|
||||||
|
.map(result => (result as PromiseRejectedResult).reason);
|
||||||
|
|
||||||
|
if (failedResults.length > 0) {
|
||||||
|
console.error('有部分请求失败:', failedResults);
|
||||||
|
logger.error('有部分请求失败:', { failedResults })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:严格筛选“审批完成且审批通过”
|
||||||
|
const approvedResults = successfulResults.filter(item => {
|
||||||
|
const status = (get(item, 'body.result.status', '') || '').toUpperCase();
|
||||||
|
const resultFlag = (get(item, 'body.result.result', '') || '').toUpperCase();
|
||||||
|
return status === 'COMPLETED'&& resultFlag === 'AGREE';
|
||||||
|
});
|
||||||
|
logger.info('importInvoiceApprovalData results', { successful: successfulResults.length, approved: approvedResults.length })
|
||||||
|
|
||||||
|
// 新增:安全 JSON 解析工具,统一归一化为数组
|
||||||
|
const safeParseArray = (val: any): any[] => {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(val)) return val;
|
||||||
|
if (typeof val === 'string' && val.trim()) {
|
||||||
|
const parsed = JSON.parse(val);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 映射为入库对象
|
||||||
|
const mappedDatas = approvedResults.map(item => {
|
||||||
|
const formComponentValues = get(item, 'body.result.formComponentValues', []);
|
||||||
|
const processInstanceId = get(item, 'processInstanceId', '');
|
||||||
|
const approvalStatus = get(item, 'body.result.status', '');
|
||||||
|
const businessId = get(item, 'body.result.businessId', '') || '';
|
||||||
|
const approvalNumberDD = get(item, 'body.result.approvalNumber', '') || get(item, 'body.result.approvalNo', '') || '';
|
||||||
|
const mappedData: any = {};
|
||||||
|
const tableMapping = FIELD_MAPPINGS[tableName as keyof typeof FIELD_MAPPINGS] || {};
|
||||||
|
const normalize = (s: any) => typeof s === 'string' ? s.replace(/\s+/g, '').replace(/[::]/g, '').replace(/(.*?)/g, '').replace(/\(.*?\)/g, '') : s;
|
||||||
|
const normalizedMap: Record<string, string> = Object.fromEntries(Object.entries(tableMapping).map(([k, v]) => [normalize(k), v as string]));
|
||||||
|
const unmappedFields: string[] = [];
|
||||||
|
const pickRowField = (rowValue: any[], candidates: string[]) => {
|
||||||
|
const norm = (s: string) => typeof s === 'string' ? s.replace(/\s+/g, '').replace(/[::]/g, '').replace(/(.*?)/g, '').replace(/\(.*?\)/g, '') : '';
|
||||||
|
const targets = candidates.map(norm);
|
||||||
|
for (const cell of rowValue || []) {
|
||||||
|
const lbl = norm(cell?.label || '');
|
||||||
|
if (targets.includes(lbl)) return cell?.value ?? '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 字段映射
|
||||||
|
|
||||||
|
formComponentValues.forEach((fv: any) => {
|
||||||
|
const fieldName = fv.name;
|
||||||
|
const fieldValue = fv.value;
|
||||||
|
|
||||||
|
const tablekey = (tableMapping as any)[fieldName] || normalizedMap[normalize(fieldName)];
|
||||||
|
|
||||||
|
if (fv.componentType === 'DDSelectField' && fv.extValue) {
|
||||||
|
try {
|
||||||
|
const extValueObj = JSON.parse(fv.extValue);
|
||||||
|
const finalValue = extValueObj.label || fieldValue;
|
||||||
|
if (tablekey && !mappedData[tablekey] && finalValue) {
|
||||||
|
mappedData[tablekey] = finalValue;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to parse extValue for field ${fieldName}:`, error);
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fv.componentType === 'DepartmentField' && fv.extValue) {
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(fv.extValue);
|
||||||
|
const obj = Array.isArray(arr) ? arr[0] : null;
|
||||||
|
const finalValue = obj?.name || obj?.fullDeptName || fieldValue;
|
||||||
|
if (tablekey && !mappedData[tablekey] && finalValue) {
|
||||||
|
mappedData[tablekey] = finalValue;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fv.componentType === 'InnerContactField' && fv.extValue) {
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(fv.extValue);
|
||||||
|
const obj = Array.isArray(arr) ? arr[0] : null;
|
||||||
|
const finalValue = obj?.name || fieldValue;
|
||||||
|
if (tablekey && !mappedData[tablekey] && finalValue) {
|
||||||
|
mappedData[tablekey] = finalValue;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fv.componentType === 'SignatureField') {
|
||||||
|
const cleaned = typeof fieldValue === 'string' ? fieldValue.replace(/`/g, '').trim() : fieldValue;
|
||||||
|
if (tablekey && !mappedData[tablekey] && cleaned) {
|
||||||
|
mappedData[tablekey] = cleaned;
|
||||||
|
}
|
||||||
|
} else if (fv.componentType === 'DDAttachment') {
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
}
|
||||||
|
} else if (fv.componentType === 'DDDateField' || fv.componentType === 'TextField') {
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tablekey && !mappedData[tablekey] && fieldValue) {
|
||||||
|
mappedData[tablekey] = fieldValue;
|
||||||
|
} else if (!tablekey) {
|
||||||
|
unmappedFields.push(fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// dataId
|
||||||
|
const idKeyName = DATA_ID_FIELD_MAPPINGS[tableName];
|
||||||
|
if (processInstanceId && idKeyName) {
|
||||||
|
mappedData[idKeyName] = processInstanceId;
|
||||||
|
}
|
||||||
|
// 新增:拆分并归一化业务开始/结束时间的区间字段
|
||||||
|
if ('businessStartTime-BusinessEndTime' in mappedData) {
|
||||||
|
const raw = mappedData['businessStartTime-BusinessEndTime'];
|
||||||
|
let start = '';
|
||||||
|
let end = '';
|
||||||
|
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
start = raw[0] ?? '';
|
||||||
|
end = raw[1] ?? '';
|
||||||
|
} else if (typeof raw === 'string') {
|
||||||
|
const arr = safeParseArray(raw);
|
||||||
|
if (arr.length > 0) {
|
||||||
|
start = arr[0] ?? '';
|
||||||
|
end = arr[1] ?? '';
|
||||||
|
} else {
|
||||||
|
// 若不是 JSON 数组字符串,兜底将整值当作开始时间
|
||||||
|
start = raw || '';
|
||||||
|
end = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若已有单字段值,则优先保留已有值
|
||||||
|
if (!mappedData.businessStartTime) mappedData.businessStartTime = start;
|
||||||
|
if (!mappedData.businessEndTime) mappedData.businessEndTime = end;
|
||||||
|
|
||||||
|
// 入库前删除临时复合键,避免 Prisma 校验报错
|
||||||
|
delete mappedData['businessStartTime-BusinessEndTime'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 协议类型代码映射(不存在置 null)
|
||||||
|
const agreementTypeCode = AGREEMENT_TYPE_CODE[AGREEMENT_TYPE_NAME[mappedData['agreementType']]] ?? null;
|
||||||
|
|
||||||
|
// 发票类型名称与代码
|
||||||
|
const invoiceTypeName: string = mappedData['invoiceType'] || '';
|
||||||
|
const invoiceCode = INVOICE_CODE[invoiceTypeName] ?? null;
|
||||||
|
|
||||||
|
// 解析两类明细表格(已存在)
|
||||||
|
const bigInvoiceAmountList = get(mappedData, 'invoice_amount_yuan_da', '');
|
||||||
|
const bigInvoiceAmountListStr = bigInvoiceAmountList ? JSON.parse(bigInvoiceAmountList) : [];
|
||||||
|
mappedData.BigInvoiceAmountList = bigInvoiceAmountListStr.map(items => {
|
||||||
|
const rowValue = items.rowValue;
|
||||||
|
// const thirdPartyCost = pickRowField(rowValue, ['预计报销第三方成本(不含税,元)','预计报销第三方成本(元)','预计报销第三方成本']);
|
||||||
|
const invoiceAmount = pickRowField(rowValue, ['开票金额(元)(不含税)', '开票金额(元)', '开票金额']) || (rowValue[1]?.value ?? '');
|
||||||
|
const bigInvoiceAmount: InvoiceApplicationAmountDetails = {
|
||||||
|
dataid: processInstanceId,
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
project_number: '',
|
||||||
|
business_group_name: rowValue[0]?.value ?? '',
|
||||||
|
project_name: '',
|
||||||
|
business_content: '',
|
||||||
|
invoice_amount: invoiceAmount,
|
||||||
|
// yu_ji_bao_xiao_di_san_fang_cheng_ben: thirdPartyCost,
|
||||||
|
}
|
||||||
|
return bigInvoiceAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
const multiProjectMergeInvoice = get(mappedData, 'multiProjectMergeInvoice', '');
|
||||||
|
const multiProjectMergeInvoiceStr = multiProjectMergeInvoice ? JSON.parse(multiProjectMergeInvoice) : [];
|
||||||
|
|
||||||
|
// 同时保留明细记录用于明细表
|
||||||
|
mappedData.multiProjectMergeInvoice =multiProjectMergeInvoiceStr.map(row => {
|
||||||
|
const rowValue = row.rowValue;
|
||||||
|
const rowField = (candidates: string[], fallbackIndex?: number) => {
|
||||||
|
const valueByLabel = pickRowField(rowValue, candidates);
|
||||||
|
if (valueByLabel !== '' && valueByLabel !== undefined && valueByLabel !== null) {
|
||||||
|
return valueByLabel;
|
||||||
|
}
|
||||||
|
if (fallbackIndex !== undefined) {
|
||||||
|
return rowValue?.[fallbackIndex]?.value ?? '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
const thirdPartyCost = pickRowField(rowValue, ['预计报销第三方成本(不含税,元)','预计报销第三方成本(元)','预计报销第三方成本']);
|
||||||
|
const multiProjectMergeInvoice: InvoiceApplicationAmountDetails = {
|
||||||
|
dataid: processInstanceId,
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
project_number: rowField(['项目编号'], 0),
|
||||||
|
project_name: rowField(['项目名称'], 1),
|
||||||
|
business_group_name: rowField(['业务组名称', '申请业务组', '业务组'], 5), // 业务组名称
|
||||||
|
business_content: rowField(['业务内容'], 6), // 业务内容
|
||||||
|
invoice_amount: rowField(['开票金额(元)', '开票金额(元)', '开票金额'], 3), // 开票金额
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben: thirdPartyCost,
|
||||||
|
}
|
||||||
|
return multiProjectMergeInvoice;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergedProjectNumbers = Array.from(new Set((multiProjectMergeInvoiceStr || []).map(row => row.rowValue[0]?.value).filter(Boolean)));
|
||||||
|
if (mergedProjectNumbers.length > 1) {
|
||||||
|
mappedData.projectNumber = mergedProjectNumbers.join(',');
|
||||||
|
mappedData.is_multi_project_merge = '是';
|
||||||
|
} else if (mergedProjectNumbers.length === 1) {
|
||||||
|
mappedData.projectNumber = mergedProjectNumbers[0];
|
||||||
|
}
|
||||||
|
// 计算多项目合并的总金额并写入主表字段(用于导出)
|
||||||
|
if (Array.isArray(multiProjectMergeInvoiceStr) && multiProjectMergeInvoiceStr.length > 0) {
|
||||||
|
const totalInvoiceAmount = multiProjectMergeInvoiceStr.reduce((sum, row) => {
|
||||||
|
const rowValue = row?.rowValue || [];
|
||||||
|
const rawAmount = pickRowField(rowValue, ['开票金额(元)', '开票金额(元)', '开票金额']) || (rowValue?.[3]?.value ?? '0');
|
||||||
|
const val = parseFloat(rawAmount);
|
||||||
|
return sum + (isNaN(val) ? 0 : val);
|
||||||
|
}, 0);
|
||||||
|
const taxRateNum = parseFloat(mappedData.invoice_tax_rate ?? '0');
|
||||||
|
if (totalInvoiceAmount > 0) {
|
||||||
|
if (!isNaN(taxRateNum) && taxRateNum > 0) {
|
||||||
|
const buHan = totalInvoiceAmount / (1 + taxRateNum);
|
||||||
|
const hanShui = totalInvoiceAmount;
|
||||||
|
mappedData.bu_han_shui_jin_e_yuan_duo = buHan.toFixed(2);
|
||||||
|
mappedData.han_shui_jin_e_yuan_duo = hanShui.toFixed(2);
|
||||||
|
} else {
|
||||||
|
// 无税率时,视为不含税=含税=开票金额
|
||||||
|
mappedData.bu_han_shui_jin_e_yuan_duo = totalInvoiceAmount.toFixed(2);
|
||||||
|
mappedData.han_shui_jin_e_yuan_duo = totalInvoiceAmount.toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:解析"单合同多家单位开票清单"
|
||||||
|
const singleContractMultiCustomer = get(mappedData, 'single_contract_multi_customer_invoice_list', '');
|
||||||
|
const singleContractMultiCustomerStr = singleContractMultiCustomer ? JSON.parse(singleContractMultiCustomer) : [];
|
||||||
|
|
||||||
|
// 仅主表记录“预计报销第三方成本(不含税,元)”字段:汇总三类明细的该字段
|
||||||
|
{
|
||||||
|
const extractThirdParty = (row: any) => {
|
||||||
|
const rv = row?.rowValue || []
|
||||||
|
const val = pickRowField(rv, ['预计报销第三方成本(不含税,元)','预计报销第三方成本(元)','预计报销第三方成本'])
|
||||||
|
const num = parseFloat((val ?? '').toString())
|
||||||
|
return Number.isNaN(num) ? 0 : num
|
||||||
|
}
|
||||||
|
const existingThirdPartyRaw = String(mappedData.yuJiBaoXiaoDiSanFangChengBenYuan ?? '').trim()
|
||||||
|
const existingThirdPartyNum = parseFloat(existingThirdPartyRaw)
|
||||||
|
const sumThirdParty =
|
||||||
|
(Array.isArray(bigInvoiceAmountListStr) ? bigInvoiceAmountListStr.reduce((s, r) => s + extractThirdParty(r), 0) : 0) +
|
||||||
|
(Array.isArray(multiProjectMergeInvoiceStr) ? multiProjectMergeInvoiceStr.reduce((s, r) => s + extractThirdParty(r), 0) : 0) +
|
||||||
|
(Array.isArray(singleContractMultiCustomerStr) ? singleContractMultiCustomerStr.reduce((s, r) => s + extractThirdParty(r), 0) : 0)
|
||||||
|
if (sumThirdParty > 0) {
|
||||||
|
mappedData.yuJiBaoXiaoDiSanFangChengBenYuan = sumThirdParty.toString()
|
||||||
|
} else if (existingThirdPartyRaw && !Number.isNaN(existingThirdPartyNum)) {
|
||||||
|
mappedData.yuJiBaoXiaoDiSanFangChengBenYuan = existingThirdPartyRaw
|
||||||
|
} else {
|
||||||
|
mappedData.yuJiBaoXiaoDiSanFangChengBenYuan = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 为单合同多家单位开票生成主表记录
|
||||||
|
const singleContractMultiCustomerMainRecords = singleContractMultiCustomerStr.map(row => {
|
||||||
|
const rowValue = row.rowValue;
|
||||||
|
const rowField = (candidates: string[], fallbackIndex?: number) => {
|
||||||
|
const valueByLabel = pickRowField(rowValue, candidates);
|
||||||
|
if (valueByLabel !== '' && valueByLabel !== undefined && valueByLabel !== null) {
|
||||||
|
return valueByLabel;
|
||||||
|
}
|
||||||
|
if (fallbackIndex !== undefined) {
|
||||||
|
return rowValue?.[fallbackIndex]?.value ?? '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
const invoiceAmountRaw = rowField(['开票金额(元)', '开票金额(元)', '开票金额'], 7);
|
||||||
|
const invoiceAmount = parseFloat(invoiceAmountRaw ?? '0');
|
||||||
|
const taxRate = parseFloat(mappedData.invoice_tax_rate ?? '0');
|
||||||
|
const thirdPartyCost = pickRowField(rowValue, ['预计报销第三方成本(不含税,元)','预计报销第三方成本(元)','预计报销第三方成本']);
|
||||||
|
|
||||||
|
// 计算不含税金额和含税金额
|
||||||
|
let buHanShuiAmount = 0;
|
||||||
|
let hanShuiAmount = 0;
|
||||||
|
|
||||||
|
if (taxRate > 0) {
|
||||||
|
// 如果税率存在,根据税率计算
|
||||||
|
buHanShuiAmount = invoiceAmount / (1 + taxRate);
|
||||||
|
hanShuiAmount = invoiceAmount;
|
||||||
|
} else {
|
||||||
|
// 如果没有税率,假设开票金额就是不含税金额
|
||||||
|
buHanShuiAmount = invoiceAmount;
|
||||||
|
hanShuiAmount = invoiceAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建主表记录,复制原记录的所有字段,但替换客户相关字段
|
||||||
|
return {
|
||||||
|
...mappedData,
|
||||||
|
dataId: processInstanceId,
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
invoiceType: invoiceTypeName || null,
|
||||||
|
invoiceCode: invoiceCode,
|
||||||
|
approvalStatus: approvalStatus,
|
||||||
|
approvalNumber: businessId,
|
||||||
|
approvalResult: '审批通过',
|
||||||
|
// 替换为明细的银行账号信息
|
||||||
|
projectNumber: rowField(['项目编号'], 0),
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: rowField(['统一社会信用代码'], 3),
|
||||||
|
bankName: rowField(['开户行名称'], 4),
|
||||||
|
bankAccount: rowField(['银行账号'], 5),
|
||||||
|
customerCompanyName: rowField(['付款单位名称', '客户单位名称'], 2),
|
||||||
|
// customer_tax_number: rowValue[3]?.value ?? '',
|
||||||
|
// customer_bank_name: rowValue[4]?.value ?? '',
|
||||||
|
// customer_bank_account: rowValue[5]?.value ?? '',
|
||||||
|
// business_content_du: rowValue[6]?.value ?? '',
|
||||||
|
invoice_amount_yuan_du: invoiceAmount.toString(),
|
||||||
|
bu_han_shui_jin_e_yuan_du: buHanShuiAmount.toFixed(2),
|
||||||
|
han_shui_jin_e_yuan_du: hanShuiAmount.toFixed(2),
|
||||||
|
// 使用明细对应的第三方成本值,而不是汇总值
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan: thirdPartyCost,
|
||||||
|
is_single_contract_multi_customer: '是',
|
||||||
|
// 确保BigInvoiceAmountList字段存在且为null
|
||||||
|
BigInvoiceAmountList: null,
|
||||||
|
// 确保multiProjectMergeInvoice字段存在且为null
|
||||||
|
multiProjectMergeInvoice: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提取项目编号到mappedData
|
||||||
|
if (singleContractMultiCustomerStr.length > 0) {
|
||||||
|
const firstRow = singleContractMultiCustomerStr[0];
|
||||||
|
const projectNumber = pickRowField(firstRow.rowValue || [], ['项目编号']) || firstRow.rowValue?.[0]?.value || '';
|
||||||
|
if (projectNumber) {
|
||||||
|
mappedData.projectNumber = projectNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedData.singleContractMultiCustomerInvoice = singleContractMultiCustomerStr.map(row => {
|
||||||
|
const rv = row.rowValue;
|
||||||
|
const rowField = (candidates: string[], fallbackIndex?: number) => {
|
||||||
|
const valueByLabel = pickRowField(rv, candidates);
|
||||||
|
if (valueByLabel !== '' && valueByLabel !== undefined && valueByLabel !== null) {
|
||||||
|
return valueByLabel;
|
||||||
|
}
|
||||||
|
if (fallbackIndex !== undefined) {
|
||||||
|
return rv?.[fallbackIndex]?.value ?? '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
const thirdPartyCost = pickRowField(rv, ['预计报销第三方成本(不含税,元)','预计报销第三方成本(元)','预计报销第三方成本']);
|
||||||
|
const detail: InvoiceApplicationAmountDetails = {
|
||||||
|
dataid: processInstanceId,
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
project_number: rowField(['项目编号'], 0),
|
||||||
|
project_name: rowField(['项目名称'], 1),
|
||||||
|
customer_company_name: rowField(['付款单位名称', '客户单位名称'], 2),
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: rowField(['统一社会信用代码'], 3),
|
||||||
|
bank_name: rowField(['开户行名称'], 4),
|
||||||
|
bank_account: rowField(['银行账号'], 5),
|
||||||
|
business_content: rowField(['开票事由', '业务内容'], 6),
|
||||||
|
invoice_amount: rowField(['开票金额(元)', '开票金额(元)', '开票金额'], 7),
|
||||||
|
payee: rowField(['付款人'], 8),
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben: thirdPartyCost,
|
||||||
|
};
|
||||||
|
return detail;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将生成的主表记录保存到mappedData中
|
||||||
|
mappedData.singleContractMultiCustomerMainRecords = singleContractMultiCustomerMainRecords;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mappedData,
|
||||||
|
dataId: processInstanceId,
|
||||||
|
agreementType: agreementTypeCode,
|
||||||
|
invoiceType: invoiceTypeName || null,
|
||||||
|
invoiceCode: invoiceCode,
|
||||||
|
approvalStatus: approvalStatus,
|
||||||
|
approvalNumber: businessId,
|
||||||
|
approvalResult: '审批通过',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("mappedDatas", mappedDatas)
|
||||||
|
logger.info('importInvoiceApprovalData mappedDatas', { count: mappedDatas.length, sampleKeys: mappedDatas[0] ? Object.keys(mappedDatas[0]).slice(0, 20) : [] })
|
||||||
|
try {
|
||||||
|
if (tableName === 'invoice_void_record') {
|
||||||
|
const recordsForInsert = mappedDatas.map(item => {
|
||||||
|
const { BigInvoiceAmountList, multiProjectMergeInvoice, singleContractMultiCustomerInvoice, multiProjectMergeMainRecords, singleContractMultiCustomerMainRecords, ...rest } = item;
|
||||||
|
const tableFieldMappings = FIELD_MAPPINGS[tableName as keyof typeof FIELD_MAPPINGS];
|
||||||
|
const idKeyNameLocal = DATA_ID_FIELD_MAPPINGS[tableName];
|
||||||
|
const filteredData: any = {};
|
||||||
|
// 只保留在字段映射中定义的字段
|
||||||
|
Object.keys(rest).forEach(key => {
|
||||||
|
if (Object.values(tableFieldMappings).includes(key)) {
|
||||||
|
filteredData[key] = rest[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 确保 dataId/data_id 字段存在
|
||||||
|
if (!filteredData[idKeyNameLocal] && item[idKeyNameLocal]) {
|
||||||
|
filteredData[idKeyNameLocal] = item[idKeyNameLocal];
|
||||||
|
}
|
||||||
|
return filteredData;
|
||||||
|
});
|
||||||
|
logger.info('importInvoiceApprovalData void main records', { count: recordsForInsert.length, sampleKeys: recordsForInsert[0] ? Object.keys(recordsForInsert[0]).slice(0, 20) : [] })
|
||||||
|
|
||||||
|
await prismaClient[tableName].createMany({
|
||||||
|
data: recordsForInsert,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
const voidInfoByPiaoHao = new Map<string, { remark?: string; cancelReason?: string; canceldataid?: string; invoice_amount_yuan?: string; hong_chong_piao_hao?: string }>();
|
||||||
|
for (const r of recordsForInsert) {
|
||||||
|
if (r.piao_hao) {
|
||||||
|
voidInfoByPiaoHao.set(r.piao_hao, {
|
||||||
|
remark: r.remark,
|
||||||
|
cancelReason: r.cancelReason,
|
||||||
|
canceldataid: r.dataId,
|
||||||
|
invoice_amount_yuan: r.invoice_amount_yuan,
|
||||||
|
hong_chong_piao_hao: r.hong_chong_piao_hao
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const piaoHaos = recordsForInsert.map(r => r.piao_hao).filter(Boolean);
|
||||||
|
const foundInvoices = piaoHaos.length > 0 ? await prismaClient.invoice_data.findMany({
|
||||||
|
where: { piao_hao: { in: piaoHaos } },
|
||||||
|
select: { dataid: true, piao_hao: true }
|
||||||
|
}) : [];
|
||||||
|
const updateOriginals: any[] = [];
|
||||||
|
const negativeRecords: any[] = [];
|
||||||
|
const normalizeAmount = (val: any): string => {
|
||||||
|
if (val === null || val === undefined) return '';
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
if (isNaN(val)) return '';
|
||||||
|
return Number(val).toFixed(2);
|
||||||
|
}
|
||||||
|
const s = String(val).trim();
|
||||||
|
if (!s) return '';
|
||||||
|
const neg = /^\(.+\)$/.test(s);
|
||||||
|
let cleaned = s.replace(/[,\s,]/g, '');
|
||||||
|
cleaned = cleaned.replace(/[¥¥元]/g, '');
|
||||||
|
cleaned = cleaned.replace(/^[\(]|[\)]$/g, '');
|
||||||
|
const num = Number(cleaned);
|
||||||
|
if (isNaN(num)) return '';
|
||||||
|
const v = neg ? -num : num;
|
||||||
|
return v.toFixed(2);
|
||||||
|
};
|
||||||
|
for (const fi of foundInvoices) {
|
||||||
|
const info = voidInfoByPiaoHao.get(fi.piao_hao);
|
||||||
|
// 从dingding_oa_invoice_application表获取发票金额信息
|
||||||
|
const invoiceApp = await prismaClient.dingding_oa_invoice_application.findFirst({
|
||||||
|
where: { dataId: fi.dataid },
|
||||||
|
select: {
|
||||||
|
invoice_amount_yuan: true,
|
||||||
|
invoice_amount_yuan_du: true,
|
||||||
|
invoice_amount_yuan_other: true,
|
||||||
|
invoice_amount_yuan_da: true,
|
||||||
|
han_shui_jin_e_yuan_du: true,
|
||||||
|
han_shui_jin_e_yuan_other: true,
|
||||||
|
han_shui_jin_e_yuan_duo: true,
|
||||||
|
han_shui_zong_jin_e_yuan_da: true,
|
||||||
|
han_shui_zong_jin_e_yuan_da_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_other: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 使用第一个非空的金额字段作为目标
|
||||||
|
const targetFields = [
|
||||||
|
invoiceApp?.invoice_amount_yuan,
|
||||||
|
invoiceApp?.invoice_amount_yuan_du,
|
||||||
|
invoiceApp?.invoice_amount_yuan_other,
|
||||||
|
invoiceApp?.invoice_amount_yuan_da,
|
||||||
|
invoiceApp?.han_shui_jin_e_yuan_du,
|
||||||
|
invoiceApp?.han_shui_jin_e_yuan_other,
|
||||||
|
invoiceApp?.han_shui_jin_e_yuan_duo,
|
||||||
|
invoiceApp?.han_shui_zong_jin_e_yuan_da,
|
||||||
|
invoiceApp?.han_shui_zong_jin_e_yuan_da_du,
|
||||||
|
invoiceApp?.bu_han_shui_jin_e_yuan_du,
|
||||||
|
invoiceApp?.bu_han_shui_jin_e_yuan_other,
|
||||||
|
invoiceApp?.bu_han_shui_jin_e_yuan_da,
|
||||||
|
invoiceApp?.bu_han_shui_jin_e_yuan_da_du,
|
||||||
|
invoiceApp?.bu_han_shui_jin_e_yuan_duo,
|
||||||
|
];
|
||||||
|
const target = targetFields.find(f => f && f.trim()) || '';
|
||||||
|
if (!target) continue;
|
||||||
|
const candidates = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { dataId: fi.dataid },
|
||||||
|
select: {
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
dataId: true,
|
||||||
|
invoice_amount_yuan: true,
|
||||||
|
invoice_amount_yuan_du: true,
|
||||||
|
invoice_amount_yuan_other: true,
|
||||||
|
invoice_amount_yuan_da: true,
|
||||||
|
han_shui_jin_e_yuan_du: true,
|
||||||
|
han_shui_jin_e_yuan_other: true,
|
||||||
|
han_shui_jin_e_yuan_duo: true,
|
||||||
|
han_shui_zong_jin_e_yuan_da: true,
|
||||||
|
han_shui_zong_jin_e_yuan_da_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_other: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da: true,
|
||||||
|
bu_han_shui_jin_e_yuan_da_du: true,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const match = candidates.filter(c => {
|
||||||
|
const fields = [
|
||||||
|
c.invoice_amount_yuan,
|
||||||
|
c.invoice_amount_yuan_du,
|
||||||
|
c.invoice_amount_yuan_other,
|
||||||
|
c.invoice_amount_yuan_da,
|
||||||
|
c.han_shui_jin_e_yuan_du,
|
||||||
|
c.han_shui_jin_e_yuan_other,
|
||||||
|
c.han_shui_jin_e_yuan_duo,
|
||||||
|
c.han_shui_zong_jin_e_yuan_da,
|
||||||
|
c.han_shui_zong_jin_e_yuan_da_du,
|
||||||
|
c.bu_han_shui_jin_e_yuan_du,
|
||||||
|
c.bu_han_shui_jin_e_yuan_other,
|
||||||
|
c.bu_han_shui_jin_e_yuan_da,
|
||||||
|
c.bu_han_shui_jin_e_yuan_da_du,
|
||||||
|
c.bu_han_shui_jin_e_yuan_duo,
|
||||||
|
];
|
||||||
|
const hasMatch = fields.some(f => {
|
||||||
|
const normalized = normalizeAmount(f);
|
||||||
|
const targetNormalized = normalizeAmount(target);
|
||||||
|
const isMatch = normalized && targetNormalized && normalized === targetNormalized;
|
||||||
|
if (isMatch) {
|
||||||
|
console.log(`匹配成功: 字段值=${f}, 规范化后=${normalized}, 目标=${target}, 目标规范化后=${targetNormalized}`);
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
});
|
||||||
|
if (!hasMatch && candidates.length > 0) {
|
||||||
|
console.log(`匹配失败: 目标金额=${target}, 目标规范化后=${normalizeAmount(target)}, 候选字段值=`, fields.map(f => `${f}->${normalizeAmount(f)}`));
|
||||||
|
}
|
||||||
|
return hasMatch;
|
||||||
|
});
|
||||||
|
if (match.length === 0) {
|
||||||
|
throw new Error(`不存在匹配记录: 票号=${fi.piao_hao}, 金额=${target}`);
|
||||||
|
} else if (match.length > 1) {
|
||||||
|
throw new Error(`存在多条匹配记录,无法唯一定位: 票号=${fi.piao_hao}, 金额=${target}`);
|
||||||
|
} else {
|
||||||
|
const m = match[0] as any;
|
||||||
|
const data: any = { remark: '发票作废' };
|
||||||
|
if (info?.cancelReason) data.cancelReason = info.cancelReason;
|
||||||
|
if (info?.canceldataid) data.canceldataid = info.canceldataid;
|
||||||
|
updateOriginals.push(
|
||||||
|
prismaClient.dingding_oa_invoice_application.update({
|
||||||
|
where: {
|
||||||
|
invoiceJobNumber_dataId: {
|
||||||
|
invoiceJobNumber: m.invoiceJobNumber,
|
||||||
|
dataId: m.dataId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const neg: any = { dataId: m.dataId };
|
||||||
|
// 复制所有可用字段,排除不需要的字段
|
||||||
|
const fieldsToExclude = new Set([
|
||||||
|
'invoiceJobNumber', 'dataId', 'BigInvoiceAmountList', 'multiProjectMergeInvoice',
|
||||||
|
'singleContractMultiCustomerInvoice', 'multiProjectMergeMainRecords',
|
||||||
|
'singleContractMultiCustomerMainRecords'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 复制所有字段
|
||||||
|
Object.keys(m).forEach(k => {
|
||||||
|
if (!fieldsToExclude.has(k) && m[k] !== undefined) {
|
||||||
|
neg[k] = m[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置作废相关字段
|
||||||
|
neg['remark'] = '发票作废';
|
||||||
|
// 添加红冲发票号(从作废申请中获取)
|
||||||
|
if (info?.hong_chong_piao_hao) {
|
||||||
|
neg['hong_chong_piao_hao'] = info.hong_chong_piao_hao;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountFields = [
|
||||||
|
'invoice_amount_yuan','invoice_amount_yuan_du','invoice_amount_yuan_other',
|
||||||
|
'bu_han_shui_jin_e_yuan_du','han_shui_jin_e_yuan_du',
|
||||||
|
'bu_han_shui_jin_e_yuan_other','han_shui_jin_e_yuan_other',
|
||||||
|
'bu_han_shui_jin_e_yuan_duo','han_shui_jin_e_yuan_duo',
|
||||||
|
'bu_han_shui_jin_e_yuan_da','han_shui_zong_jin_e_yuan_da',
|
||||||
|
'bu_han_shui_jin_e_yuan_da_du','han_shui_zong_jin_e_yuan_da_du',
|
||||||
|
'yuJiBaoXiaoDiSanFangChengBenYuan'
|
||||||
|
];
|
||||||
|
amountFields.forEach(f => {
|
||||||
|
const raw = (m as any)[f];
|
||||||
|
const num = typeof raw === 'string' && raw.trim() !== '' ? parseFloat(raw.replace(/,/g, '')) : NaN;
|
||||||
|
if (!Number.isNaN(num)) {
|
||||||
|
const negVal = num > 0 ? -num : num;
|
||||||
|
(neg as any)[f] = negVal.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
negativeRecords.push(neg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateOriginals.length > 0 || negativeRecords.length > 0) {
|
||||||
|
await prismaClient.$transaction([
|
||||||
|
...updateOriginals,
|
||||||
|
...(negativeRecords.length > 0 ? [prismaClient.dingding_oa_invoice_application.createMany({
|
||||||
|
data: negativeRecords,
|
||||||
|
skipDuplicates: true
|
||||||
|
})] : [])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const mainTableMappings = FIELD_MAPPINGS[tableName as keyof typeof FIELD_MAPPINGS] || {};
|
||||||
|
const mainAllowedKeys = new Set<string>([
|
||||||
|
...Object.values(mainTableMappings),
|
||||||
|
DATA_ID_FIELD_MAPPINGS[tableName as keyof typeof DATA_ID_FIELD_MAPPINGS]
|
||||||
|
].filter(Boolean) as string[]);
|
||||||
|
const sanitizeMain = (rec: any) => {
|
||||||
|
const out: any = {};
|
||||||
|
for (const key of mainAllowedKeys) {
|
||||||
|
if (rec && Object.prototype.hasOwnProperty.call(rec, key)) {
|
||||||
|
out[key] = rec[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idKeyNameLocal = DATA_ID_FIELD_MAPPINGS[tableName as keyof typeof DATA_ID_FIELD_MAPPINGS];
|
||||||
|
if (idKeyNameLocal && rec?.[idKeyNameLocal] && !out[idKeyNameLocal]) {
|
||||||
|
out[idKeyNameLocal] = rec[idKeyNameLocal];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
const mainRecords = [
|
||||||
|
...mappedDatas.filter(item => (
|
||||||
|
!item.singleContractMultiCustomerMainRecords || item.singleContractMultiCustomerMainRecords.length === 0
|
||||||
|
)).map(item => {
|
||||||
|
const { BigInvoiceAmountList, multiProjectMergeInvoice, singleContractMultiCustomerInvoice, multiProjectMergeMainRecords, singleContractMultiCustomerMainRecords, ...rest } = item;
|
||||||
|
return sanitizeMain(rest);
|
||||||
|
}),
|
||||||
|
...mappedDatas.flatMap(item => {
|
||||||
|
const records: any[] = [];
|
||||||
|
if (item.singleContractMultiCustomerMainRecords && item.singleContractMultiCustomerMainRecords.length > 0) {
|
||||||
|
records.push(...item.singleContractMultiCustomerMainRecords.map(record => {
|
||||||
|
const { BigInvoiceAmountList, multiProjectMergeInvoice, singleContractMultiCustomerInvoice, multiProjectMergeMainRecords, singleContractMultiCustomerMainRecords, ...filteredRecord } = record;
|
||||||
|
return sanitizeMain(filteredRecord);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return records;
|
||||||
|
})
|
||||||
|
].filter(r => r && Object.keys(r).length > 0);
|
||||||
|
const detailAllowedKeys = new Set<string>([
|
||||||
|
'dataid','agreementType','project_number','business_group_name','project_name','customer_company_name','tong_yi_she_hui_xin_yong_dai_ma','bank_name','bank_account','business_content','invoice_amount','payee','yu_ji_bao_xiao_di_san_fang_cheng_ben'
|
||||||
|
]);
|
||||||
|
const detailRecords = mappedDatas.flatMap(item => [
|
||||||
|
...item.BigInvoiceAmountList,
|
||||||
|
...item.multiProjectMergeInvoice,
|
||||||
|
...(item.singleContractMultiCustomerInvoice || []),
|
||||||
|
]).map(rec => {
|
||||||
|
const out: any = {};
|
||||||
|
for (const key of detailAllowedKeys) {
|
||||||
|
if (rec && Object.prototype.hasOwnProperty.call(rec, key)) {
|
||||||
|
out[key] = rec[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}).filter(r => r && Object.keys(r).length > 0);
|
||||||
|
const txOps: any[] = [];
|
||||||
|
if (mainRecords.length > 0) {
|
||||||
|
txOps.push(prismaClient[tableName].createMany({
|
||||||
|
data: mainRecords,
|
||||||
|
skipDuplicates: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (detailRecords.length > 0) {
|
||||||
|
txOps.push(prismaClient.invoice_amount_details.createMany({
|
||||||
|
data: detailRecords,
|
||||||
|
skipDuplicates: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (txOps.length > 0) {
|
||||||
|
await prismaClient.$transaction(txOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('插入数据时出错:', error);
|
||||||
|
logger.error('插入数据时出错:', { error })
|
||||||
|
return res.status(400).json({ code:400, message: `数据插入到${tableName}表中失败`, error: error, })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newInstanceIds.length === 0) {
|
||||||
|
logger.info(`本次无新增数据插入到${tableName}表; count: 0; []`,)
|
||||||
|
return res.status(200).json({code:0,message: `本次无新增数据`, newInstanceIds })
|
||||||
|
}
|
||||||
|
logger.info(`数据处理完成: ${tableName}; 待处理实例数: ${newInstanceIds.length}; ${JSON.stringify(newInstanceIds)}`,)
|
||||||
|
return res.status(200).json({code:0,message: `数据处理完成`, newInstanceIds })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
logger.error('获取 token 时出错:', { error })
|
||||||
|
return res.status(500).json({ code:500, message: '获取 token 时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
src/pages/api/dingding/util.ts
Normal file
53
src/pages/api/dingding/util.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { get } from 'lodash'
|
||||||
|
import OAClient from '@/api/dingding/oa'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function getInstanceIdList({
|
||||||
|
accessToken,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
maxResults,
|
||||||
|
processCode
|
||||||
|
}: {
|
||||||
|
accessToken: string
|
||||||
|
startTime: number
|
||||||
|
endTime: number
|
||||||
|
maxResults: number
|
||||||
|
processCode: string
|
||||||
|
}) {
|
||||||
|
let allList = [];
|
||||||
|
let nextToken: number = 0;
|
||||||
|
const lpiRes = await OAClient.listProcessInstanceIdsWithOptions({
|
||||||
|
processCode: processCode,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
nextToken: nextToken,
|
||||||
|
maxResults: maxResults,
|
||||||
|
dingtalkAccessToken: accessToken,
|
||||||
|
statuses: ['COMPLETED']
|
||||||
|
}) as any;
|
||||||
|
const list = get(lpiRes, 'body.result.list', [])
|
||||||
|
nextToken = get(lpiRes, 'body.result.nextToken', undefined)
|
||||||
|
allList.push(...list)
|
||||||
|
// 不为空表示有更多数据。
|
||||||
|
while (nextToken) {
|
||||||
|
const lpiRes = await OAClient.listProcessInstanceIdsWithOptions({
|
||||||
|
processCode: processCode,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
nextToken: nextToken,
|
||||||
|
maxResults: maxResults,
|
||||||
|
dingtalkAccessToken: accessToken,
|
||||||
|
statuses: ['COMPLETED']
|
||||||
|
}) as any;
|
||||||
|
// TODO 如果有问题 这里可以尝试等待
|
||||||
|
// const { list = [], nextToken: newNextToken } = lpiRes?.body?.result ? lpiRes?.body?.result : {};
|
||||||
|
const list = get(lpiRes, 'body.result.list', [])
|
||||||
|
nextToken = get(lpiRes, 'body.result.nextToken', undefined)
|
||||||
|
allList = [...allList, ...list];
|
||||||
|
console.log("listProcessInstanceIdsWithOptions", list, nextToken);
|
||||||
|
}
|
||||||
|
return allList;
|
||||||
|
}
|
||||||
13
src/pages/api/dingding/webhook.ts
Normal file
13
src/pages/api/dingding/webhook.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
data: boolean;
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ data: true, success: true });
|
||||||
|
}
|
||||||
87
src/pages/api/expense_reimbursement/import.ts
Normal file
87
src/pages/api/expense_reimbursement/import.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { InvoiceApplicationItem } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
const ExcelToMySQLFieldMapping = {
|
||||||
|
'序号': 'xuhao',
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'项目号': 'project_number',
|
||||||
|
'日期': 'date',
|
||||||
|
'凭证号': 'voucher_number',
|
||||||
|
'分录号': 'entry_number',
|
||||||
|
'摘要': 'summary',
|
||||||
|
'科目编码': 'account_code',
|
||||||
|
'科目名称': 'account_name',
|
||||||
|
'辅助项': 'auxiliary_items',
|
||||||
|
'币种': 'currency',
|
||||||
|
'借方原币': 'debit_original_currency',
|
||||||
|
'借方本币': 'debit_local_currency',
|
||||||
|
'贷方原币': 'credit_original_currency',
|
||||||
|
'贷方本币': 'credit_local_currency',
|
||||||
|
'SR号': 'sr_number',
|
||||||
|
'共享凭证号': 'shared_voucher_number',
|
||||||
|
'费用预算项目': 'expense_budget_item',
|
||||||
|
'成本中心': 'cost_center',
|
||||||
|
'标准名称': 'standard_name',
|
||||||
|
'一级费用类型': 'expense_type_level1',
|
||||||
|
'二级费用类型': 'expense_type_level2',
|
||||||
|
'三级费用类型': 'expense_type_level3',
|
||||||
|
'金额': 'amount',
|
||||||
|
'创建时间': 'created_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'updated_time'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: InvoiceApplicationItem[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
let expenseReimbursementDataList = [];
|
||||||
|
// TODO 可以查询一次搞重新修改 看业务需求
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const expenseReimbursementData: any = {
|
||||||
|
data_id: rawItem['数据id'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
expenseReimbursementData[dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
expenseReimbursementDataList.push(expenseReimbursementData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据到expense_reimbursement表
|
||||||
|
await prismaClient.expense_reimbursement.createMany({
|
||||||
|
data: expenseReimbursementDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: '费用报销数据导入成功'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
60
src/pages/api/getInvoiceApprovalData.ts
Normal file
60
src/pages/api/getInvoiceApprovalData.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* @description
|
||||||
|
* 技术方案:
|
||||||
|
* https://docs.dingtalk.com/i/nodes/Obva6QBXJw6YEvDyila56vb0Vn4qY5Pr
|
||||||
|
*/
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import AuthClient from '../../api/dingding/auth'
|
||||||
|
import OAClient from '../../api/dingding/oa'
|
||||||
|
|
||||||
|
const PROCESS_CODE = process.env.DINGDING_INVOICE_APPROVAL_PROCESS_CODE ?? ''
|
||||||
|
type ResponseData = {
|
||||||
|
message: string
|
||||||
|
testjsonres?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
console.log("启动")
|
||||||
|
// 1 获得token
|
||||||
|
const accessToken = await AuthClient.getAccessToken();
|
||||||
|
console.log("获得token", accessToken);
|
||||||
|
// 2 拿到所有的实例id
|
||||||
|
const startTime = dayjs().subtract(60, 'day').valueOf();
|
||||||
|
|
||||||
|
const lpiRes = await OAClient.listProcessInstanceIdsWithOptions({
|
||||||
|
processCode: PROCESS_CODE,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: undefined,
|
||||||
|
nextToken: 0,
|
||||||
|
maxResults: 5,
|
||||||
|
dingtalkAccessToken: accessToken,
|
||||||
|
}) as any; // TODO
|
||||||
|
const { list = [], nextToken } = lpiRes?.body?.result ? lpiRes?.body?.result : {}
|
||||||
|
// TODO List
|
||||||
|
// 根据processid 拿到
|
||||||
|
let testjsonres = null
|
||||||
|
if (list[0]) {
|
||||||
|
const pinstance = await OAClient.getProcessInstanceWithOptions({
|
||||||
|
processInstanceId: list[0],
|
||||||
|
dingtalkAccessToken: accessToken
|
||||||
|
})
|
||||||
|
testjsonres = pinstance;
|
||||||
|
console.log("---------------------------------")
|
||||||
|
console.log("pinstance", JSON.stringify(pinstance))
|
||||||
|
console.log("---------------------------------")
|
||||||
|
}
|
||||||
|
console.log("listProcessInstanceIdsWithOptions", list, nextToken)
|
||||||
|
// 3
|
||||||
|
res.status(200).json({ message: `Hello from Next.js! `, testjsonres: testjsonres })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
res.status(500).json({ message: '获取 token 时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
167
src/pages/api/invoice/cancelInvoice.ts
Normal file
167
src/pages/api/invoice/cancelInvoice.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { logger } from '@/api/winston/logger'
|
||||||
|
|
||||||
|
interface RequestBody {
|
||||||
|
cancelType: number
|
||||||
|
cancelReason: number
|
||||||
|
dataId: string
|
||||||
|
piaoHao: string
|
||||||
|
remarks: string
|
||||||
|
invoiceAmount?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResponseData {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: any
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 验证请求方法
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({
|
||||||
|
code: 405,
|
||||||
|
message: 'Method not allowed',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证请求体参数
|
||||||
|
const { cancelType,cancelReason, dataId, piaoHao, remarks, invoiceAmount }: RequestBody = req.body
|
||||||
|
|
||||||
|
if (!piaoHao) {
|
||||||
|
return res.status(400).json({
|
||||||
|
code: 400,
|
||||||
|
message: 'piaoHao为必填项',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const normalizeAmount = (val: any): string => {
|
||||||
|
if (val === null || val === undefined) return ''
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
if (isNaN(val)) return ''
|
||||||
|
return String(val)
|
||||||
|
}
|
||||||
|
const s = String(val).trim()
|
||||||
|
if (!s) return ''
|
||||||
|
const neg = /^\(.+\)$/.test(s)
|
||||||
|
let cleaned = s.replace(/[,\s,]/g, '')
|
||||||
|
cleaned = cleaned.replace(/[¥¥元]/g, '')
|
||||||
|
cleaned = cleaned.replace(/^[\(]|[\)]$/g, '')
|
||||||
|
const num = Number(cleaned)
|
||||||
|
if (isNaN(num)) return ''
|
||||||
|
return String(neg ? -num : num)
|
||||||
|
}
|
||||||
|
const targetAmountStr = normalizeAmount(invoiceAmount)
|
||||||
|
if (!targetAmountStr) {
|
||||||
|
return res.status(400).json({
|
||||||
|
code: 400,
|
||||||
|
message: '缺少有效的发票金额,用于唯一定位主表记录'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始数据库事务
|
||||||
|
await prismaClient.$transaction(async (prisma) => {
|
||||||
|
// 0. 如果未传 dataId,则根据票号查询
|
||||||
|
let resolvedDataId = dataId
|
||||||
|
if (!resolvedDataId) {
|
||||||
|
const inv = await prisma.invoice_data.findUnique({
|
||||||
|
where: { piao_hao: piaoHao }
|
||||||
|
})
|
||||||
|
resolvedDataId = inv?.dataid || ''
|
||||||
|
}
|
||||||
|
if (!resolvedDataId) {
|
||||||
|
throw new Error('根据票号未找到对应的 dataId')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 更新invoice_data表中的invoice_type_enum字段(按票号定位更稳妥)
|
||||||
|
await prisma.invoice_data.update({
|
||||||
|
where: { piao_hao: piaoHao },
|
||||||
|
data: {
|
||||||
|
invoice_type_enum: cancelType,
|
||||||
|
updated_at: new Date(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 在发票作废记录表中添加操作记录
|
||||||
|
await prisma.invoice_void_record.create({
|
||||||
|
data: {
|
||||||
|
dataId: resolvedDataId,
|
||||||
|
piao_hao: piaoHao,
|
||||||
|
cancelReason: String(cancelReason),
|
||||||
|
zuo_fei_shi_you: remarks || '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. 更新主表:dataId 下可能多条记录,需要按发票金额匹配
|
||||||
|
const candidates = await prisma.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { dataId: resolvedDataId },
|
||||||
|
select: {
|
||||||
|
invoiceJobNumber: true,
|
||||||
|
dataId: true,
|
||||||
|
|
||||||
|
invoice_amount_yuan_du: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const normalizeToFixed2 = (s?: string | null) => {
|
||||||
|
const n = normalizeAmount(s ?? '')
|
||||||
|
if (!n) return ''
|
||||||
|
return Number(n).toFixed(2)
|
||||||
|
}
|
||||||
|
const targetFixed = Number(targetAmountStr).toFixed(2)
|
||||||
|
const isMatch = (c: any) => {
|
||||||
|
const fields = [
|
||||||
|
|
||||||
|
c.invoice_amount_yuan_du
|
||||||
|
]
|
||||||
|
return fields.some(f => normalizeToFixed2(f) === targetFixed)
|
||||||
|
}
|
||||||
|
const matches = candidates.filter(isMatch)
|
||||||
|
if (matches.length === 0) {
|
||||||
|
throw new Error('未找到与发票金额匹配的主表记录')
|
||||||
|
}
|
||||||
|
if (matches.length > 1) {
|
||||||
|
throw new Error('存在多条金额匹配记录,无法唯一定位,请提供更明确的金额或其他限定条件')
|
||||||
|
}
|
||||||
|
const m = matches[0]
|
||||||
|
await prisma.dingding_oa_invoice_application.update({
|
||||||
|
where: {
|
||||||
|
// 复合主键
|
||||||
|
invoiceJobNumber_dataId: {
|
||||||
|
invoiceJobNumber: m.invoiceJobNumber,
|
||||||
|
dataId: m.dataId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
remark: '发票作废',
|
||||||
|
cancelReason: String(cancelReason)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info(`发票作废成功: dataId=${resolvedDataId}, piaoHao=${piaoHao}, amount=${targetAmountStr}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '发票作废成功',
|
||||||
|
data: {
|
||||||
|
dataId: dataId,
|
||||||
|
piaoHao,
|
||||||
|
cancelType,
|
||||||
|
remarks,
|
||||||
|
invoiceAmount: invoiceAmount ?? null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('发票作废失败:', error)
|
||||||
|
return res.status(500).json({
|
||||||
|
code: 500,
|
||||||
|
message: '服务器内部错误',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/pages/api/invoice/exportInvoiceData.ts
Normal file
2
src/pages/api/invoice/exportInvoiceData.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// 发票类型:从开票申请中获取
|
||||||
|
const fa_piao_lei_xing = item.invoiceType || '';
|
||||||
4
src/pages/api/invoice/exportInvoiceDataFile.ts
Normal file
4
src/pages/api/invoice/exportInvoiceDataFile.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// ... existing code ...
|
||||||
|
// 发票类型:从开票申请中获取
|
||||||
|
const fa_piao_lei_xing = item.invoiceType || '';
|
||||||
|
// ... existing code ...
|
||||||
241
src/pages/api/invoice/getInvoiceData.ts
Normal file
241
src/pages/api/invoice/getInvoiceData.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { AGREEMENT_TYPES, AGREEMENT_TYPE_CODE } from '@/CONSTANTS'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any;
|
||||||
|
pagination?: {
|
||||||
|
total: number
|
||||||
|
pageNum: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method != 'POST') {
|
||||||
|
return res.status(400).json({ message: '请求方法错误' });
|
||||||
|
}
|
||||||
|
// 从查询参数获取分页信息,设置默认值
|
||||||
|
const pageNum = parseInt(req.body.pageNum as string) || 1;
|
||||||
|
const pageSize = parseInt(req.body.pageSize as string) || 10;
|
||||||
|
const skip = (pageNum - 1) * pageSize;
|
||||||
|
|
||||||
|
const { agreementType, projectNumber, customerCompanyName, joinType = 'left', issueStatus = 'unissued' } = req.body;
|
||||||
|
const whereConditions: any = {};
|
||||||
|
if (agreementType) {
|
||||||
|
if (agreementType === '1') {
|
||||||
|
whereConditions.agreementType = { in: ['1', '3'] };
|
||||||
|
} else if (agreementType === '2') {
|
||||||
|
whereConditions.agreementType = { in: ['2', '4'] };
|
||||||
|
} else {
|
||||||
|
whereConditions.agreementType = agreementType as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (projectNumber) {
|
||||||
|
whereConditions.projectNumber = {
|
||||||
|
contains: projectNumber as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (customerCompanyName) {
|
||||||
|
whereConditions.customerCompanyName = {
|
||||||
|
contains: customerCompanyName as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 仅查询非作废票
|
||||||
|
// whereConditions.invoice_type_enum = 0;
|
||||||
|
const voidRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { remark: '发票作废' },
|
||||||
|
select: { dataId: true, invoiceJobNumber: true }
|
||||||
|
});
|
||||||
|
const voidPairs = voidRecords
|
||||||
|
.filter(r => r.dataId && r.invoiceJobNumber)
|
||||||
|
.map(r => ({ dataId: r.dataId as string, invoiceJobNumber: r.invoiceJobNumber as string }));
|
||||||
|
if (voidPairs.length > 0) {
|
||||||
|
whereConditions.AND = [
|
||||||
|
...(whereConditions.AND || []),
|
||||||
|
{
|
||||||
|
NOT: {
|
||||||
|
OR: voidPairs.map(pair => ({
|
||||||
|
dataId: pair.dataId,
|
||||||
|
invoiceJobNumber: pair.invoiceJobNumber
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// 已开票/未开票/全部
|
||||||
|
if (issueStatus === 'issued') {
|
||||||
|
whereConditions.AND = [
|
||||||
|
...(whereConditions.AND || []),
|
||||||
|
{ piao_hao: { not: '' } },
|
||||||
|
{ NOT: { piao_hao: null } }
|
||||||
|
];
|
||||||
|
} else if (issueStatus === 'unissued') {
|
||||||
|
whereConditions.OR = [
|
||||||
|
...(whereConditions.OR || []),
|
||||||
|
{ piao_hao: '' },
|
||||||
|
{ piao_hao: null }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(pageNum) || isNaN(pageSize) || pageNum < 1 || pageSize < 1) {
|
||||||
|
return res.status(400).json({ message: '无效的分页参数' });
|
||||||
|
}
|
||||||
|
// 如果是inner 那么是完成开票结果表,否则是全量数据
|
||||||
|
const viewModel = joinType === 'inner' ?
|
||||||
|
prismaClient.invoice_application_match_inner_view :
|
||||||
|
prismaClient.invoice_application_match_view;
|
||||||
|
// 获取分页数据
|
||||||
|
const invoiceData = await viewModel.findMany({
|
||||||
|
where: whereConditions,
|
||||||
|
skip,
|
||||||
|
take: pageSize,
|
||||||
|
orderBy: [
|
||||||
|
{ invoiceJobNumber: 'asc' }
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// 统计总数:已开票按票号去重后的总数,其它保持原逻辑
|
||||||
|
const total = (issueStatus === 'issued')
|
||||||
|
? (await viewModel.findMany({
|
||||||
|
where: whereConditions,
|
||||||
|
select: { piao_hao: true },
|
||||||
|
distinct: ['piao_hao'],
|
||||||
|
})).length
|
||||||
|
: await viewModel.count({
|
||||||
|
where: whereConditions
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提取大协议的dataId
|
||||||
|
const largeAgreementIds = invoiceData
|
||||||
|
.filter(item => item.agreementType === '1')
|
||||||
|
.map(item => item.dataId);
|
||||||
|
|
||||||
|
// 从invoice_amount_details获取大协议的业务内容
|
||||||
|
const largeAgreementDetails = await prismaClient.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: {
|
||||||
|
in: largeAgreementIds
|
||||||
|
},
|
||||||
|
agreementType: AGREEMENT_TYPE_CODE[AGREEMENT_TYPES.LARGE]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建一个映射,存储每个dataid对应的多个business_content
|
||||||
|
// 用于快速获取大协议的业务内容字段
|
||||||
|
const businessContentMap = new Map<string, string[]>();
|
||||||
|
largeAgreementDetails.forEach((detail, idx) => {
|
||||||
|
if (detail.dataid && detail.business_content) {
|
||||||
|
if (!businessContentMap.has(detail.dataid)) {
|
||||||
|
businessContentMap.set(detail.dataid, []);
|
||||||
|
}
|
||||||
|
businessContentMap.get(detail.dataid)?.push(`${idx}. [${detail.project_name}]${detail.business_content}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在这里增加逻辑
|
||||||
|
// 1 增加 customerCompanyName 取自 invoiceData.customerCompanyNameLargeAgreement(如果协议类型是大协议 和 invoiceData.customerCompanyNameIndependentAgreement 如果协议类型是独立协议
|
||||||
|
// 2 账期 invoice_period 是 开票日期和回款日期两个字段都存在 那么账期= 回款日期 payment_date -开票日期 invoice_date
|
||||||
|
const mappedRows = invoiceData
|
||||||
|
.map((ele) => {
|
||||||
|
// 添加调试日志
|
||||||
|
// console.log('原始数据:', {
|
||||||
|
// agreementType: ele.agreementType,
|
||||||
|
// is_single_contract_multi_customer: ele.is_single_contract_multi_customer,
|
||||||
|
// bu_han_shui_jin_e_yuan_duo: ele.bu_han_shui_jin_e_yuan_duo,
|
||||||
|
// han_shui_jin_e_yuan_duo: ele.han_shui_jin_e_yuan_duo
|
||||||
|
// });
|
||||||
|
|
||||||
|
const result = processInvoiceDataItem(ele, businessContentMap);
|
||||||
|
if (String(ele.dataId || '') === 'ggxiANvuSHy_Vz0mOR_38Q08211767677530') {
|
||||||
|
console.log('[invoice-amount-debug]', {
|
||||||
|
dataId: ele.dataId,
|
||||||
|
invoiceJobNumber: ele.invoiceJobNumber,
|
||||||
|
agreementType: ele.agreementType,
|
||||||
|
is_single_contract_multi_customer: ele.is_single_contract_multi_customer,
|
||||||
|
is_multi_project_merge: ele.is_multi_project_merge,
|
||||||
|
bu_han_shui_jin_e_yuan_du: ele.bu_han_shui_jin_e_yuan_du,
|
||||||
|
bu_han_shui_jin_e_yuan_duo: ele.bu_han_shui_jin_e_yuan_duo,
|
||||||
|
han_shui_jin_e_yuan_du: ele.han_shui_jin_e_yuan_du,
|
||||||
|
han_shui_jin_e_yuan_duo: ele.han_shui_jin_e_yuan_duo,
|
||||||
|
result_bu_han_shui_jin_e: result.bu_han_shui_jin_e,
|
||||||
|
result_han_shui_zong_jin_e: result.han_shui_zong_jin_e
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('处理结果:', {
|
||||||
|
// bu_han_shui_jin_e: result.bu_han_shui_jin_e,
|
||||||
|
// han_shui_zong_jin_e: result.han_shui_zong_jin_e
|
||||||
|
// });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
// 添加去重逻辑:基于 projectNumber 去重,确保每个项目编号只出现一次
|
||||||
|
// .filter((item, index, array) =>
|
||||||
|
// array.findIndex(i => i.projectNumber === item.projectNumber) === index
|
||||||
|
// )
|
||||||
|
// 兜底:将没有票号的记录排在前面,其余按开票作业序号升序(防止字符串排序或空值干扰)
|
||||||
|
|
||||||
|
const dedupByPiaoHao = (rows: any[]) => {
|
||||||
|
const chosen = new Map<string, any>();
|
||||||
|
for (const r of rows) {
|
||||||
|
const key = String(r.piao_hao || '').trim();
|
||||||
|
if (!key) continue;
|
||||||
|
const prev = chosen.get(key);
|
||||||
|
const currNo = Number(r.invoiceJobNumber) || 0;
|
||||||
|
const prevNo = Number(prev?.invoiceJobNumber) || 0;
|
||||||
|
if (!prev || currNo > prevNo) {
|
||||||
|
chosen.set(key, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const withTicket = Array.from(chosen.values());
|
||||||
|
const noTicket = rows.filter(r => !String(r.piao_hao || '').trim());
|
||||||
|
return [...noTicket, ...withTicket];
|
||||||
|
};
|
||||||
|
const rowsForSort = issueStatus === 'issued' ? dedupByPiaoHao(mappedRows) : mappedRows;
|
||||||
|
const modifiedInvoiceData = rowsForSort.sort((a, b) => {
|
||||||
|
const aNoTicket = !a.piao_hao || String(a.piao_hao).trim() === '';
|
||||||
|
const bNoTicket = !b.piao_hao || String(b.piao_hao).trim() === '';
|
||||||
|
if (aNoTicket && !bNoTicket) return -1;
|
||||||
|
if (!aNoTicket && bNoTicket) return 1;
|
||||||
|
const numA = Number(a.invoiceJobNumber) || 0;
|
||||||
|
const numB = Number(b.invoiceJobNumber) || 0;
|
||||||
|
return numA - numB;
|
||||||
|
});
|
||||||
|
// 拉取到开票信息之后,再根据项目信息做统计来生成应收账款台账
|
||||||
|
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
error: null,
|
||||||
|
message: '数据获取成功',
|
||||||
|
data: {
|
||||||
|
data: modifiedInvoiceData,
|
||||||
|
pagination: {
|
||||||
|
total,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
totalPages: Math.ceil(total / pageSize)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
res.status(500).json({ message: '获取数据时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
213
src/pages/api/invoice/importInvoiceFinishedData.ts
Normal file
213
src/pages/api/invoice/importInvoiceFinishedData.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { InvoiceDataDBItem } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { logger } from '@/api/winston/logger';
|
||||||
|
const normalizeAmount = (val: any): string => {
|
||||||
|
if (val === null || val === undefined) return '0'
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
if (isNaN(val)) return '0'
|
||||||
|
return String(val)
|
||||||
|
}
|
||||||
|
const s = String(val).trim()
|
||||||
|
if (!s) return '0'
|
||||||
|
const neg = /^\(.+\)$/.test(s)
|
||||||
|
let cleaned = s.replace(/[,\s,]/g, '')
|
||||||
|
cleaned = cleaned.replace(/[¥¥元]/g, '')
|
||||||
|
cleaned = cleaned.replace(/^[\(]|[\)]$/g, '')
|
||||||
|
const num = Number(cleaned)
|
||||||
|
if (isNaN(num)) return '0'
|
||||||
|
return String(neg ? -num : num)
|
||||||
|
}
|
||||||
|
// 添加日期转换函数
|
||||||
|
const convertExcelDate = (excelDate: any): string => {
|
||||||
|
if (!excelDate) return '';
|
||||||
|
|
||||||
|
// 如果是数字(Excel序列号)
|
||||||
|
if (typeof excelDate === 'number') {
|
||||||
|
// Excel日期从1900年1月1日开始,但Excel有个bug:1900年被当作闰年
|
||||||
|
const excelEpoch = new Date(1900, 0, 1);
|
||||||
|
const date = new Date(excelEpoch.getTime() + (excelDate - 1) * 24 * 60 * 60 * 1000);
|
||||||
|
return dayjs(date).format('YYYY/MM/DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是字符串,直接返回
|
||||||
|
return String(excelDate);
|
||||||
|
};
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
if (!fileBuffer) {
|
||||||
|
return res.status(400).json({ error: '未提供文件' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: {
|
||||||
|
数据ID: string;
|
||||||
|
项目编号: string;
|
||||||
|
单位: string;
|
||||||
|
集团内外: string;
|
||||||
|
票号: string;
|
||||||
|
不含税金额: string;
|
||||||
|
税额: string;
|
||||||
|
含税金额: string;
|
||||||
|
申请人: string;
|
||||||
|
专业组: string;
|
||||||
|
开票日期: string;
|
||||||
|
账期: string;
|
||||||
|
回款日期: string;
|
||||||
|
回款金额: string;
|
||||||
|
业务内容: string;
|
||||||
|
}[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
|
||||||
|
// 数据校验和转换
|
||||||
|
let datas: InvoiceDataDBItem[] = [];
|
||||||
|
const validationErrors: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const rowNumber = i; // Excel行号从2开始(第1行是表头)
|
||||||
|
// 基础数据校验
|
||||||
|
if (!rawItem['票号'] || rawItem['票号'].trim() === '') {
|
||||||
|
validationErrors.push(`第${rowNumber}行:票号为必填项`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// if (isNaN(Number(rawItem['回款金额']))) {
|
||||||
|
// validationErrors.push(`第${rowNumber}行:回款金额必须为数字`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// if (isNaN(Number(rawItem['不含税金额']))) {
|
||||||
|
// validationErrors.push(`第${rowNumber}行:不含税金额必须为数字`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// if (isNaN(Number(rawItem['税额']))) {
|
||||||
|
// validationErrors.push(`第${rowNumber}行:税额必须为数字`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// if (isNaN(Number(rawItem['含税金额']))) {
|
||||||
|
// validationErrors.push(`第${rowNumber}行:含税金额必须为数字`);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
const kai_piao_ri_qi = convertExcelDate(rawItem['开票日期'])
|
||||||
|
const hui_kuan_ri_qi = convertExcelDate(rawItem['回款日期'])
|
||||||
|
// 账期计算:优先使用导入文件中的"账期",否则按日期差计算
|
||||||
|
let zhang_qi = '';
|
||||||
|
const importedZhangQi = (rawItem['账期'] ?? '').toString().trim();
|
||||||
|
if (importedZhangQi) {
|
||||||
|
zhang_qi = importedZhangQi;
|
||||||
|
} else if (rawItem['开票日期'] && rawItem['回款日期']) {
|
||||||
|
// 两个日期都存在:有回款日期时,账期应该为空
|
||||||
|
zhang_qi = '';
|
||||||
|
} else if (rawItem['开票日期'] && !rawItem['回款日期']) {
|
||||||
|
// 只有开票日期:账期 = 当前日期 - 开票日期(单位:天)
|
||||||
|
const kp = dayjs(convertExcelDate(rawItem['开票日期']));
|
||||||
|
const now = dayjs();
|
||||||
|
if (kp.isValid()) {
|
||||||
|
const dayDiff = now.diff(kp, 'day');
|
||||||
|
zhang_qi = dayDiff.toString();
|
||||||
|
} else {
|
||||||
|
logger.error(`第${rowNumber}行:开票日期格式错误,开票日期=${rawItem['开票日期']}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 转换为数据库模型
|
||||||
|
datas.push({
|
||||||
|
dataid: `${rawItem['数据ID']}`,
|
||||||
|
piao_hao: `${rawItem['票号']}`,
|
||||||
|
kai_piao_ri_qi: `${kai_piao_ri_qi}`,
|
||||||
|
hui_kuan_ri_qi: `${hui_kuan_ri_qi}`,
|
||||||
|
hui_kuan_jin_e: normalizeAmount(rawItem['回款金额']),
|
||||||
|
zhang_qi: zhang_qi,
|
||||||
|
invoice_type_enum: 0,
|
||||||
|
customer_company_name: `${rawItem['单位']}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回校验错误
|
||||||
|
if (validationErrors.length > 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: '数据校验失败',
|
||||||
|
errors: validationErrors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let createCount = 0;
|
||||||
|
let updateCount = 0;
|
||||||
|
// 数据库存在性校验和upsert操作
|
||||||
|
await prismaClient.$transaction(async (prisma) => {
|
||||||
|
const dataIds = datas.map(item => item.dataid);
|
||||||
|
|
||||||
|
// 批量查询已存在记录
|
||||||
|
const existingRecords = await prisma.invoice_data.findMany({
|
||||||
|
where: {
|
||||||
|
OR: datas.map(item => ({
|
||||||
|
piao_hao: item.piao_hao
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建唯一键集合
|
||||||
|
const existingIdSet = new Set(existingRecords.map(r => r.piao_hao));
|
||||||
|
|
||||||
|
// 分离需要创建和更新的数据
|
||||||
|
const createData = datas.filter(item => !existingIdSet.has(item.piao_hao));
|
||||||
|
const updateData = datas.filter(item => existingIdSet.has(item.piao_hao));
|
||||||
|
createCount = createData.length;
|
||||||
|
updateCount = updateData.length;
|
||||||
|
// 批量创建新数据
|
||||||
|
if (createData.length > 0) {
|
||||||
|
await prisma.invoice_data.createMany({
|
||||||
|
data: createData,
|
||||||
|
skipDuplicates: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐条更新已有数据(updateMany不支持批量不同更新)
|
||||||
|
// 逐条更新已有数据(updateMany不支持批量不同更新)
|
||||||
|
for (const item of updateData) {
|
||||||
|
await prisma.invoice_data.update(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
piao_hao: item.piao_hao
|
||||||
|
},
|
||||||
|
data: item
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
maxWait: 30000, // 最大等待时间30秒
|
||||||
|
timeout: 60000, // 超时时间60秒
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
createCount,
|
||||||
|
updateCount,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
message: '开票完成数据导入成功',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(500).json({ code: -1, error: 'Internal server error', stack: error });
|
||||||
|
}
|
||||||
|
})
|
||||||
41
src/pages/api/project/get.ts
Normal file
41
src/pages/api/project/get.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import { getBussinessDetailsList } from './util'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有project数据
|
||||||
|
const projects = await prismaClient.project.findMany();
|
||||||
|
const excelDatas = await getBussinessDetailsList(projects)
|
||||||
|
// excelDatas 输出到excel文件并吐出去
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
const ws = XLSX.utils.json_to_sheet(excelDatas, {
|
||||||
|
// header: [
|
||||||
|
// ...(excelDatas?.[0] ?? {}).key
|
||||||
|
// ]
|
||||||
|
});
|
||||||
|
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
|
||||||
|
XLSX.writeFile(wb, "output.xlsx");
|
||||||
|
// Create a new workbook and append the worksheet
|
||||||
|
const workbook = XLSX.utils.book_new()
|
||||||
|
XLSX.utils.book_append_sheet(workbook, ws, '开票申请数据')
|
||||||
|
|
||||||
|
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' })
|
||||||
|
// Set headers for file download
|
||||||
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
res.setHeader('Content-Disposition', 'attachment; filename=project-all-data.xlsx')
|
||||||
|
// Send the Excel file
|
||||||
|
res.write(buffer)
|
||||||
|
res.end()
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error });
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/pages/api/project/getByProjectNumber.ts
Normal file
36
src/pages/api/project/getByProjectNumber.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(400).json({ code: 400, message: '请求方法错误' })
|
||||||
|
}
|
||||||
|
const { project_number } = req.body || {}
|
||||||
|
if (!project_number || typeof project_number !== 'string') {
|
||||||
|
return res.status(400).json({ code: 400, message: '缺少项目编号' })
|
||||||
|
}
|
||||||
|
const records = await prismaClient.project.findMany({
|
||||||
|
where: { project_id: project_number }
|
||||||
|
})
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取成功',
|
||||||
|
data: { data: records }
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).json({ code: 500, message: '获取数据时出错', error })
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/pages/api/project/import.ts
Normal file
75
src/pages/api/project/import.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { InvoiceApplicationItem } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
const ExcelToMySQLFieldMapping = {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'组别缩写': 'group_abbreviation',
|
||||||
|
'上一项目编号': 'previous_project_id',
|
||||||
|
'项目编号': 'project_id',
|
||||||
|
'项目名称': 'project_name',
|
||||||
|
"关联合同编号": 'contract_id',
|
||||||
|
"合同名称": 'contract_name',
|
||||||
|
'客户名称': 'client_name',
|
||||||
|
'交付区域(省/市)': 'delivery_area',
|
||||||
|
'单位性质': 'unit_nature',
|
||||||
|
'项目主担': 'project_leader',
|
||||||
|
'项目组别': 'project_group',
|
||||||
|
'创建时间': 'create_time',
|
||||||
|
'创建者': 'creator',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_title',
|
||||||
|
'更新时间': 'update_time'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: InvoiceApplicationItem[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
let projectDataList = [];
|
||||||
|
// TODO 可以查询一次搞重新修改 看业务需求
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const projectData: any = {
|
||||||
|
data_id: rawItem['数据id'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
projectData[dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
projectDataList.push(projectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据到project表
|
||||||
|
await prismaClient.project.createMany({
|
||||||
|
data: projectDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: '项目立项数据导入成功'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
91
src/pages/api/project/ledger.ts
Normal file
91
src/pages/api/project/ledger.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: any[]
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(400).json({ code: 400, message: '请求方法错误,只支持GET请求' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取查询参数
|
||||||
|
const {
|
||||||
|
project_id,
|
||||||
|
project_name,
|
||||||
|
project_leader,
|
||||||
|
client_name,
|
||||||
|
project_department,
|
||||||
|
group_abbreviation
|
||||||
|
} = req.query
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
const whereConditions: any = {}
|
||||||
|
|
||||||
|
if (project_id) {
|
||||||
|
whereConditions.project_id = {
|
||||||
|
contains: project_id as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_name) {
|
||||||
|
whereConditions.project_name = {
|
||||||
|
contains: project_name as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_leader) {
|
||||||
|
whereConditions.project_leader = {
|
||||||
|
contains: project_leader as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_name) {
|
||||||
|
whereConditions.client_name = {
|
||||||
|
contains: client_name as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_department) {
|
||||||
|
whereConditions.project_department = {
|
||||||
|
contains: project_department as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group_abbreviation) {
|
||||||
|
whereConditions.group_abbreviation = {
|
||||||
|
contains: group_abbreviation as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有项目数据(不分页)
|
||||||
|
const records = await prismaClient.project.findMany({
|
||||||
|
where: whereConditions,
|
||||||
|
orderBy: {
|
||||||
|
create_time: 'desc'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取项目台账数据成功',
|
||||||
|
data: records
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取项目台账数据时出错:', error)
|
||||||
|
return res.status(500).json({
|
||||||
|
code: 500,
|
||||||
|
message: '获取项目台账数据时出错',
|
||||||
|
error: error instanceof Error ? error.message : '未知错误'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
206
src/pages/api/project/ledgerAll.ts
Normal file
206
src/pages/api/project/ledgerAll.ts
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { Prisma } from '@prisma/client'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any
|
||||||
|
pagination?: {
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(400).json({ code: 400, message: '请求方法错误,只支持POST请求' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取查询参数
|
||||||
|
const {
|
||||||
|
project_number,
|
||||||
|
project_leader,
|
||||||
|
project_name,
|
||||||
|
client_name,
|
||||||
|
project_department,
|
||||||
|
group_abbreviation
|
||||||
|
} = req.body
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
const conditions: any[] = []
|
||||||
|
|
||||||
|
if (project_number) {
|
||||||
|
conditions.push(Prisma.sql`project_id LIKE ${'%' + project_number + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_leader) {
|
||||||
|
conditions.push(Prisma.sql`project_leader LIKE ${'%' + project_leader + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_name) {
|
||||||
|
conditions.push(Prisma.sql`project_name LIKE ${'%' + project_name + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_name) {
|
||||||
|
conditions.push(Prisma.sql`client_name LIKE ${'%' + client_name + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project_department) {
|
||||||
|
conditions.push(Prisma.sql`project_department LIKE ${'%' + project_department + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group_abbreviation) {
|
||||||
|
conditions.push(Prisma.sql`group_abbreviation LIKE ${'%' + group_abbreviation + '%'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建WHERE子句
|
||||||
|
let whereClause = Prisma.sql``
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
whereClause = Prisma.sql`WHERE ${conditions[0]}`
|
||||||
|
for (let i = 1; i < conditions.length; i++) {
|
||||||
|
whereClause = Prisma.sql`${whereClause} AND ${conditions[i]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有项目数据(不分页)
|
||||||
|
const projects: any[] = await prismaClient.$queryRaw(Prisma.sql`
|
||||||
|
SELECT * FROM project
|
||||||
|
${whereClause}
|
||||||
|
ORDER BY CAST(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||||
|
project_id,
|
||||||
|
'A',''),'B',''),'C',''),'D',''),'E',''),'F',''),'G',''),'H',''),'I',''),'J',''),
|
||||||
|
'K',''),'L',''),'M',''),'N',''),'O',''),'P',''),'Q',''),'R',''),'S',''),'T',''),
|
||||||
|
'U',''),'V',''),'W',''),'X',''),'Y',''),'Z',''),
|
||||||
|
'a',''),'b',''),'c',''),'d',''),'e',''),'f',''),'g',''),'h',''),'i',''),'j',''),
|
||||||
|
'k',''),'l',''),'m',''),'n',''),'o',''),'p',''),'q',''),'r',''),'s',''),'t',''),
|
||||||
|
'u',''),'v',''),'w',''),'x',''),'y',''),'z','')
|
||||||
|
AS UNSIGNED) DESC
|
||||||
|
`)
|
||||||
|
|
||||||
|
// 查询销售合同数据 - 通过合同编号关联
|
||||||
|
const contractIds = projects.map(p => p.contract_id).filter(Boolean)
|
||||||
|
const salesContracts = await prismaClient.procurement_contract_list.findMany({
|
||||||
|
where: {
|
||||||
|
contract_number: {
|
||||||
|
in: contractIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
contract_number: true,
|
||||||
|
contract_name: true,
|
||||||
|
contract_amount: true,
|
||||||
|
xiangduifang: true,
|
||||||
|
performance_start_time: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构建返回数据
|
||||||
|
const ledgerData = projects.map((project, index) => {
|
||||||
|
const projectSalesContracts = salesContracts.filter(
|
||||||
|
contract => contract.project_number === project.project_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
序号: index + 1,
|
||||||
|
项目编号: project.project_id || '',
|
||||||
|
项目名称: project.project_name || '',
|
||||||
|
客户名称: project.client_name || '',
|
||||||
|
交付区域: project.delivery_area || null,
|
||||||
|
单位性质: project.unit_nature || '',
|
||||||
|
项目主担: project.project_leader || '',
|
||||||
|
项目组别: project.project_group || '',
|
||||||
|
销售订单数量: projectSalesContracts.length,
|
||||||
|
销售合同金额: projectSalesContracts.reduce((sum, contract) => {
|
||||||
|
return sum + parseFloat(contract.contract_amount || '0')
|
||||||
|
}, 0).toString(),
|
||||||
|
采购订单数量: 0,
|
||||||
|
采购合同金额: '0',
|
||||||
|
预计总支出_不含税: '0',
|
||||||
|
开票时间: '',
|
||||||
|
开票收入_不含税: '0',
|
||||||
|
是否交付: '否',
|
||||||
|
划入交付组收入: '待定TODO',
|
||||||
|
开票金额_含税: '0',
|
||||||
|
已回款: '0',
|
||||||
|
是否回款: '否',
|
||||||
|
未回款金额: '0',
|
||||||
|
回款时间: '',
|
||||||
|
已报销: '0',
|
||||||
|
SR号: '',
|
||||||
|
预计未报销金额: '0',
|
||||||
|
已支付: '0',
|
||||||
|
预计项目收益: '0',
|
||||||
|
系统项目收益: '0',
|
||||||
|
现金流: '0',
|
||||||
|
价值创造: '0',
|
||||||
|
salesContracts: projectSalesContracts.map(contract => ({
|
||||||
|
data_id: contract.data_id,
|
||||||
|
initiator: contract.initiator || '',
|
||||||
|
contract_number: contract.contract_number || '',
|
||||||
|
contract_name: contract.contract_name || '',
|
||||||
|
lvyue_faqi_bumen: contract.lvyue_faqi_bumen || '',
|
||||||
|
operator: contract.operator || '',
|
||||||
|
xiangduifang: contract.xiangduifang || '',
|
||||||
|
is_open: contract.is_open || '',
|
||||||
|
contract_amount: contract.contract_amount || '0.00',
|
||||||
|
remarks: contract.remarks || '',
|
||||||
|
performance_start_time: contract.performance_start_time || '',
|
||||||
|
hetong_yuanjian_saomiaojian_gaizhangban: contract.hetong_yuanjian_saomiaojian_gaizhangban || '',
|
||||||
|
dingtalk_procurement_approval_form: contract.dingtalk_procurement_approval_form,
|
||||||
|
contract_attachment_scan: contract.contract_attachment_scan || '',
|
||||||
|
creation_time: contract.creation_time || '',
|
||||||
|
creator: contract.creator,
|
||||||
|
approval_result: contract.approval_result,
|
||||||
|
approval_status: contract.approval_status,
|
||||||
|
update_time: contract.update_time || '',
|
||||||
|
creator_department: contract.creator_department || '',
|
||||||
|
approval_form_title: contract.approval_form_title || ''
|
||||||
|
})),
|
||||||
|
purchaseContractsList: [],
|
||||||
|
modifiedProjectInvoiceData: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构造分页信息(不分页,但保持结构一致)
|
||||||
|
const total = ledgerData.length
|
||||||
|
const pagination = {
|
||||||
|
total,
|
||||||
|
page: 1,
|
||||||
|
pageSize: total,
|
||||||
|
totalPages: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
message: '获取项目台账数据成功',
|
||||||
|
data: {
|
||||||
|
data: ledgerData,
|
||||||
|
pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取项目台账数据时出错:', error)
|
||||||
|
return res.status(500).json({
|
||||||
|
code: 500,
|
||||||
|
message: '获取项目台账数据时出错',
|
||||||
|
error: error instanceof Error ? error.message : '未知错误'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
546
src/pages/api/project/util.ts
Normal file
546
src/pages/api/project/util.ts
Normal file
@@ -0,0 +1,546 @@
|
|||||||
|
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { XIANGMU_ZUBIE_MAP, ABBR_ZUBIE_MAP } from '@/CONSTANTS'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
|
export async function getBussinessDetailsList(projects, kai_piao_ri_qi?: unknown) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const businessContentMap = new Map<string, string[]>();// <-- 一个空对象为了复用函数
|
||||||
|
// 1 拿到所有的项目id
|
||||||
|
const projectNumbers = projects.map(project => project.project_id).filter(Boolean);
|
||||||
|
const projectNumberSet = new Set(projectNumbers);
|
||||||
|
const splitProjectNumbers = (value: unknown): string[] => {
|
||||||
|
return String(value ?? '')
|
||||||
|
.split(/[,,、\s]+/)
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
const normalizeDate = (input: unknown): dayjs.Dayjs | undefined => {
|
||||||
|
if (input === null || input === undefined) return undefined;
|
||||||
|
if (typeof input === 'number' && Number.isFinite(input)) {
|
||||||
|
const parsedNum = dayjs(input);
|
||||||
|
return parsedNum.isValid() ? parsedNum : undefined;
|
||||||
|
}
|
||||||
|
const s = String(input).trim();
|
||||||
|
if (!s) return undefined;
|
||||||
|
const cleaned = s.replace(/\//g, '-').split(' ')[0];
|
||||||
|
const parsed = dayjs(cleaned);
|
||||||
|
if (!parsed.isValid()) return undefined;
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
const parseInvoiceRange = (raw: unknown): { start?: dayjs.Dayjs, end?: dayjs.Dayjs } => {
|
||||||
|
if (!raw) return {};
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
const s = normalizeDate(raw[0]);
|
||||||
|
const e = normalizeDate(raw[1] ?? raw[0]);
|
||||||
|
return {
|
||||||
|
start: s ? s.startOf('day') : undefined,
|
||||||
|
end: e ? e.endOf('day') : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (typeof raw === 'object') {
|
||||||
|
const obj: any = raw;
|
||||||
|
const s = normalizeDate(obj?.start ?? obj?.startTime ?? obj?.begin ?? obj?.beginTime ?? obj?.[0]);
|
||||||
|
const e = normalizeDate(obj?.end ?? obj?.endTime ?? obj?.finish ?? obj?.finishTime ?? obj?.[1] ?? obj?.start);
|
||||||
|
return {
|
||||||
|
start: s ? s.startOf('day') : undefined,
|
||||||
|
end: e ? e.endOf('day') : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const text = String(raw).trim();
|
||||||
|
if (!text) return {};
|
||||||
|
if (text.startsWith('[')) {
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(text);
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
const s = normalizeDate(arr[0]);
|
||||||
|
const e = normalizeDate(arr[1] ?? arr[0]);
|
||||||
|
return {
|
||||||
|
start: s ? s.startOf('day') : undefined,
|
||||||
|
end: e ? e.endOf('day') : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
const parts = text.split(/[,,、\s~~至到]+/).map(s => s.trim()).filter(Boolean);
|
||||||
|
if (parts.length > 0) {
|
||||||
|
const s = normalizeDate(parts[0]);
|
||||||
|
const e = normalizeDate(parts[1] ?? parts[0]);
|
||||||
|
return {
|
||||||
|
start: s ? s.startOf('day') : undefined,
|
||||||
|
end: e ? e.endOf('day') : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const invoiceDateRange = parseInvoiceRange(kai_piao_ri_qi);
|
||||||
|
const isInInvoiceDateRange = (value: unknown): boolean => {
|
||||||
|
if (!invoiceDateRange.start && !invoiceDateRange.end) return true;
|
||||||
|
const d = normalizeDate(value);
|
||||||
|
if (!d) return false;
|
||||||
|
if (invoiceDateRange.start && d.isBefore(invoiceDateRange.start)) return false;
|
||||||
|
if (invoiceDateRange.end && d.isAfter(invoiceDateRange.end)) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// 2 先拉取发票数据用于建立项目号与销售合同号映射
|
||||||
|
|
||||||
|
|
||||||
|
// 根据项目号 从 invoice_application_match_view 中拿到开票信息(排除作废)
|
||||||
|
const voidRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { remark: '发票作废' },
|
||||||
|
select: { dataId: true }
|
||||||
|
});
|
||||||
|
const voidIds = voidRecords.map(r => r.dataId).filter(Boolean);
|
||||||
|
const invoiceData = await prismaClient.invoice_application_match_view.findMany({
|
||||||
|
where: {
|
||||||
|
OR: projectNumbers.map(p => ({ projectNumber: { contains: p } })),
|
||||||
|
...(voidIds.length > 0 ? { dataId: { notIn: voidIds } } : {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredInvoiceData = invoiceData.filter(item => {
|
||||||
|
const tokens = splitProjectNumbers(item.projectNumber);
|
||||||
|
return tokens.some(t => projectNumberSet.has(t));
|
||||||
|
});
|
||||||
|
const invoiceDataIds = Array.from(new Set(filteredInvoiceData.map(item => item.dataId).filter(Boolean)));
|
||||||
|
const invoiceAmountDetails = invoiceDataIds.length > 0 ? await prismaClient.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: {
|
||||||
|
in: invoiceDataIds
|
||||||
|
},
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
dataid: true,
|
||||||
|
project_number: true,
|
||||||
|
invoice_amount: true,
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben: true
|
||||||
|
}
|
||||||
|
}) : [];
|
||||||
|
const detailInvoiceAmountByDataIdProject = new Map<string, BigNumber>();
|
||||||
|
const detailThirdPartyCostByDataIdProject = new Map<string, BigNumber>();
|
||||||
|
const detailThirdPartyCostProvidedKeys = new Set<string>();
|
||||||
|
invoiceAmountDetails.forEach(item => {
|
||||||
|
const key = `${item.dataid || ''}||${item.project_number || ''}`;
|
||||||
|
const raw = String(item.invoice_amount ?? '').replace(/,/g, '').trim();
|
||||||
|
const amount = raw ? new BigNumber(raw) : new BigNumber(0);
|
||||||
|
if (!amount.isFinite()) return;
|
||||||
|
const current = detailInvoiceAmountByDataIdProject.get(key) || new BigNumber(0);
|
||||||
|
detailInvoiceAmountByDataIdProject.set(key, current.plus(amount));
|
||||||
|
const thirdPartyRaw = String(item.yu_ji_bao_xiao_di_san_fang_cheng_ben ?? '').replace(/,/g, '').trim();
|
||||||
|
if (thirdPartyRaw !== '') {
|
||||||
|
const thirdPartyAmount = new BigNumber(thirdPartyRaw);
|
||||||
|
if (thirdPartyAmount.isFinite()) {
|
||||||
|
detailThirdPartyCostProvidedKeys.add(key);
|
||||||
|
const currentThirdParty = detailThirdPartyCostByDataIdProject.get(key) || new BigNumber(0);
|
||||||
|
detailThirdPartyCostByDataIdProject.set(key, currentThirdParty.plus(thirdPartyAmount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const salesContractToProjectSetMap = new Map<string, Set<string>>();
|
||||||
|
for (const item of filteredInvoiceData) {
|
||||||
|
const contractNumber = item.salesContractNumber;
|
||||||
|
if (!contractNumber) continue;
|
||||||
|
const tokens = splitProjectNumbers(item.projectNumber);
|
||||||
|
if (tokens.length === 0) continue;
|
||||||
|
const existing = salesContractToProjectSetMap.get(contractNumber) ?? new Set<string>();
|
||||||
|
tokens.forEach(t => existing.add(t));
|
||||||
|
salesContractToProjectSetMap.set(contractNumber, existing);
|
||||||
|
}
|
||||||
|
const invoiceDataSalesContractNumbers = Array.from(salesContractToProjectSetMap.keys());
|
||||||
|
|
||||||
|
// 3 根据项目编号或关联销售合同编号查采购订单
|
||||||
|
const purchaseContractsOrder = await prismaClient.purchase_contract_order_management.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据销售合同编号在procurement_contract_list表中筛选出所有销售合同
|
||||||
|
const salesContracts = await prismaClient.procurement_contract_list.findMany({
|
||||||
|
where: {
|
||||||
|
contract_number: {
|
||||||
|
in: invoiceDataSalesContractNumbers
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将销售合同对应的项目号塞入数组
|
||||||
|
const salesContractsWithProjectNumbers = [];
|
||||||
|
|
||||||
|
// 为每个项目单独处理其相关的销售合同
|
||||||
|
for (const project of projects) {
|
||||||
|
const projectNumber = project.project_id;
|
||||||
|
if (!projectNumber) continue;
|
||||||
|
|
||||||
|
// 找出与当前项目相关的销售合同编号
|
||||||
|
const projectSalesContractNumberSet = new Set<string>();
|
||||||
|
for (const [contractNumber, projectSet] of salesContractToProjectSetMap.entries()) {
|
||||||
|
if (projectSet.has(projectNumber)) {
|
||||||
|
projectSalesContractNumberSet.add(contractNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectSalesContractNumberSet.size === 0) continue;
|
||||||
|
|
||||||
|
// 筛选出当前项目相关的销售合同
|
||||||
|
const projectSalesContracts = salesContracts.filter(contract =>
|
||||||
|
projectSalesContractNumberSet.has(contract.contract_number || '')
|
||||||
|
);
|
||||||
|
|
||||||
|
// 将销售合同与项目编号关联
|
||||||
|
const projectSalesContractsWithNumbers = projectSalesContracts.map(contract => ({
|
||||||
|
...contract,
|
||||||
|
project_number: projectNumber
|
||||||
|
}));
|
||||||
|
|
||||||
|
salesContractsWithProjectNumbers.push(...projectSalesContractsWithNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据项目号 从 expense_reimbursement 中拿到费用报销信息
|
||||||
|
const expenseReimbursementData = await prismaClient.expense_reimbursement.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从 实际支付表内拿到项目号 对应的 期末余额
|
||||||
|
const actualPaymentData = await prismaClient.actual_payment.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 1 提取出 projects 表内所有的client_name 之后去 customer_management_info内提取对应的 customer_type 形成KV映射
|
||||||
|
const customerTypeMap = new Map<string, string>();
|
||||||
|
const customerNames = projects.map(project => project.client_name).filter(Boolean);
|
||||||
|
const customerManagementInfo = await prismaClient.customer_management_info.findMany({
|
||||||
|
where: {
|
||||||
|
customer_unit_name: {
|
||||||
|
in: customerNames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
customerManagementInfo.forEach(item => {
|
||||||
|
customerTypeMap.set(item.customer_unit_name, item.customer_type || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// excelDatas 对象存储每一行的数据
|
||||||
|
const excelDatas = []
|
||||||
|
projects.map((project, idx) => {
|
||||||
|
// 专业组逻辑:提取项目编号的所有字母部分映射,如果没有项目编号则为客户服务室
|
||||||
|
const getProjectGroupByProjectId = (projectId: string | null | undefined): string => {
|
||||||
|
if (!projectId) {
|
||||||
|
return '客户服务室';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目编号的所有字母部分(匹配连续的字母字符,不限位数)
|
||||||
|
const letterMatch = projectId.match(/^[A-Za-z]+/);
|
||||||
|
const prefix = letterMatch ? letterMatch[0] : '';
|
||||||
|
|
||||||
|
// 根据字母前缀映射专业组
|
||||||
|
const groupMapping: { [key: string]: string } = {
|
||||||
|
'YY': '运营室',
|
||||||
|
'ZP': '人员招聘项目组',
|
||||||
|
'ZW': '总务服务项目组',
|
||||||
|
'XC': '薪酬核算项目组',
|
||||||
|
'TX': '退休衔接服务项目组',
|
||||||
|
'TC': '统筹支持项目组',
|
||||||
|
'RZL': '人员入转离项目组',
|
||||||
|
'PJ': '人才评鉴项目组',
|
||||||
|
'HW': '会务服务项目组',
|
||||||
|
'GJ': '国际化服务项目组',
|
||||||
|
'GZ': '管理咨询项目组',
|
||||||
|
'KF': '客户服务室',
|
||||||
|
'FL': '福利业务项目组',
|
||||||
|
'FW': '法务合规中心',
|
||||||
|
'SJ': '数字化与交付项目组',
|
||||||
|
'KJ': '科技培训项目组',
|
||||||
|
'JG': '经营管理项目组',
|
||||||
|
'JN': '技能培训项目组',
|
||||||
|
'DJ': '党建培训项目组',
|
||||||
|
'JH': '计划核定项目组',
|
||||||
|
'LGB': '老干部服务项目组'
|
||||||
|
};
|
||||||
|
|
||||||
|
return groupMapping[prefix] || '客户服务室';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 逻辑以 excel 为标准逻辑
|
||||||
|
// 1 项目类
|
||||||
|
const data = {
|
||||||
|
'序号': idx + 1,
|
||||||
|
'项目编号': project.project_id,
|
||||||
|
'项目名称': project.project_name,
|
||||||
|
'客户名称': project.client_name,
|
||||||
|
'交付区域': project.delivery_area,
|
||||||
|
'单位性质': customerTypeMap.get(project.client_name || '') || '', //
|
||||||
|
'项目主担': project.project_leader,
|
||||||
|
'项目组别': getProjectGroupByProjectId(project.project_id),
|
||||||
|
// '项目组别': XIANGMU_ZUBIE_MAP[ABBR_ZUBIE_MAP[project.group_abbreviation]], //
|
||||||
|
}
|
||||||
|
// 2 合同大类
|
||||||
|
// 销售合同 采购合同
|
||||||
|
// 销售合同/订单数量 =COUNTIFS(合同管理!D:D,C6,合同管理!C:C,$X$2)
|
||||||
|
// 销售合同号 =IF(X6=1,VLOOKUP($X$2&C6,合同管理!A:E,5,0),"-")
|
||||||
|
// 销售合同金额 =SUMIFS(合同管理!F:F,合同管理!D:D,C6,合同管理!C:C,$X$2)
|
||||||
|
// 客户名称 =IF(X6=1,VLOOKUP($X$2&C6,合同管理!A:H,8,0),"-")
|
||||||
|
//
|
||||||
|
// 采购合同/订单数量 =COUNTIFS(合同管理!D:D,C6,合同管理!C:C,$AB$2)
|
||||||
|
// 采购合同号 =IF(AB6=1,VLOOKUP(C6,合同管理!D:E,2,0),"-")
|
||||||
|
// 采购合同金额 =SUMIFS(合同管理!F:F,合同管理!D:D,C6,合同管理!C:C,$AB$2)
|
||||||
|
// 供应商名称 =IF(AB6=1,VLOOKUP($AB$2&C6,合同管理!A:H,8,0),"-")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const sales_contract = {
|
||||||
|
'销售订单数量': 0,//
|
||||||
|
// '销售合同号': '-',
|
||||||
|
'销售合同金额': new BigNumber(0),// string 或者 bignumber
|
||||||
|
// '客户名称': '-',
|
||||||
|
}
|
||||||
|
// 销售订单数量 这个变量等于 salesContractsWithProjectNumbers 里面项目号一致的合同的数量
|
||||||
|
const salesContractsObj = salesContractsWithProjectNumbers.filter(item => (item.project_number || '') === project.project_id);
|
||||||
|
sales_contract['销售订单数量'] = salesContractsObj.length
|
||||||
|
// sales_contract['销售合同号'] = salesContractsObj.length === 1 ? salesContractsObj[0].contract_name || '-' : '-';
|
||||||
|
sales_contract['销售合同金额'] = salesContractsObj.length > 0 ?
|
||||||
|
salesContractsObj.reduce((sum, contract) => (sum).plus(new BigNumber(contract?.contract_amount || 0)), new BigNumber(0)) : new BigNumber(0);
|
||||||
|
// sales_contract['客户名称'] = salesContractsObj.length === 1 ? salesContractsObj[0].xiangduifang || '-' : '-';
|
||||||
|
|
||||||
|
const purchase_contract = {
|
||||||
|
'采购订单数量': 0,
|
||||||
|
// '采购合同号': '-',
|
||||||
|
'采购合同金额': new BigNumber(0),
|
||||||
|
// '供应商名称': '-',
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 采购订单数量 这个变量等于 purchaseContractsOrder 里面项目号一致的合同的数量
|
||||||
|
const purchaseContractsList = purchaseContractsOrder.filter(item => item.project_number === project.project_id);
|
||||||
|
// 根据合同号去重
|
||||||
|
const purchaseContractsListDeduplication = purchaseContractsList.reduce((acc, current) => {
|
||||||
|
const existing = acc.find(item => item.associated_sales_contract_number === current.associated_sales_contract_number);
|
||||||
|
if (!existing) {
|
||||||
|
acc.push(current);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
purchase_contract['采购订单数量'] = purchaseContractsList.length
|
||||||
|
// purchase_contract['采购合同号'] = purchaseContractsList.length === 1 ? purchaseContractsObj[0].contract_number || '-' : '-';
|
||||||
|
purchase_contract['采购合同金额'] = purchaseContractsListDeduplication.length > 0 ?
|
||||||
|
purchaseContractsListDeduplication.reduce((sum, contract) => (sum).plus(new BigNumber(contract?.he_tong_zong_jin_e_HanShui || 0)), new BigNumber(0)) : new BigNumber(0);
|
||||||
|
// purchase_contract['供应商名称'] = purchaseContractsList.length === 1 ? purchaseContractsObj[0].xiangduifang || '-' : '-';
|
||||||
|
|
||||||
|
|
||||||
|
// 提取项目号一致的开票数据
|
||||||
|
const projectInvoiceData = filteredInvoiceData.filter(item => {
|
||||||
|
const tokens = splitProjectNumbers(item.projectNumber);
|
||||||
|
return tokens.includes(project.project_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const modifiedProjectInvoiceData = projectInvoiceData.map((ele) => processInvoiceDataItem(ele, businessContentMap));
|
||||||
|
const projectInvoiceDataInRange = projectInvoiceData.filter(item => isInInvoiceDateRange(item.kai_piao_ri_qi));
|
||||||
|
const modifiedProjectInvoiceDataInRange = projectInvoiceDataInRange.map((ele) => processInvoiceDataItem(ele, businessContentMap));
|
||||||
|
// 接下来是金额组
|
||||||
|
// "预计总支出 (不含税)" 开票时间 " 开票收入(不含税) " 是否交付 " 划入交付组收入 " " 开票金额(含税) " 已回款 是否回款 未回款金额 回款时间
|
||||||
|
const invoiceExcelData = {
|
||||||
|
'预计总支出 (不含税)': new BigNumber(0),
|
||||||
|
'开票时间': '-', // =IF(AI74=0," ",VLOOKUP(C74,'发票台账23-25'!B:K,10,0))
|
||||||
|
'开票收入(不含税)': new BigNumber(0),
|
||||||
|
'是否交付': '-',
|
||||||
|
'划入交付组收入': '待定TODO',
|
||||||
|
'开票金额(含税)': new BigNumber(0),
|
||||||
|
'已回款': new BigNumber(0),
|
||||||
|
'是否回款': '-',
|
||||||
|
'未回款金额': new BigNumber(0),
|
||||||
|
'回款时间': '-',
|
||||||
|
}
|
||||||
|
// modifiedProjectInvoiceData 是为了拿到 税额因为他们要判断类型很复杂
|
||||||
|
// 预计总支出= 所有的预计支付第三方成本的和
|
||||||
|
const expenseByInvoiceKey = new Map<string, BigNumber>();
|
||||||
|
projectInvoiceDataInRange.forEach(item => {
|
||||||
|
const key = `${item.dataId || ''}||${item.invoiceJobNumber || ''}`;
|
||||||
|
if (!expenseByInvoiceKey.has(key)) {
|
||||||
|
expenseByInvoiceKey.set(key, new BigNumber(item.yuJiBaoXiaoDiSanFangChengBenYuan || 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const projectInvoiceDataIdsInRange = Array.from(new Set(
|
||||||
|
projectInvoiceDataInRange.map(item => String(item.dataId || '')).filter(Boolean)
|
||||||
|
));
|
||||||
|
const detailThirdPartyFromDetails = projectInvoiceDataIdsInRange.reduce((sum, dataId) => {
|
||||||
|
const key = `${dataId}||${project.project_id || ''}`;
|
||||||
|
return sum.plus(detailThirdPartyCostByDataIdProject.get(key) || new BigNumber(0));
|
||||||
|
}, new BigNumber(0));
|
||||||
|
const hasDetailThirdPartyValue = projectInvoiceDataIdsInRange.some(dataId =>
|
||||||
|
detailThirdPartyCostProvidedKeys.has(`${dataId}||${project.project_id || ''}`)
|
||||||
|
);
|
||||||
|
if (hasDetailThirdPartyValue) {
|
||||||
|
invoiceExcelData['预计总支出 (不含税)'] = detailThirdPartyFromDetails;
|
||||||
|
} else {
|
||||||
|
// 明细表未维护第三方成本时,回退主表原有取值逻辑
|
||||||
|
invoiceExcelData['预计总支出 (不含税)'] = Array.from(expenseByInvoiceKey.values()).reduce((sum, amt) => (sum).plus(amt), new BigNumber(0));
|
||||||
|
}
|
||||||
|
// 开票收入不函数 == SUMIF('发票台账23-25'!B:B,C21,'发票台账23-25'!F:F)
|
||||||
|
const nonDuoMainBuHanTotal = projectInvoiceDataInRange.reduce((sum, rawItem, idx) => {
|
||||||
|
const duoRaw = rawItem.bu_han_shui_jin_e_yuan_duo;
|
||||||
|
const isDuoMain = duoRaw !== null && duoRaw !== undefined && String(duoRaw).trim() !== '';
|
||||||
|
if (isDuoMain) return sum;
|
||||||
|
const processed = modifiedProjectInvoiceDataInRange[idx];
|
||||||
|
return sum.plus(new BigNumber(processed?.bu_han_shui_jin_e || 0));
|
||||||
|
}, new BigNumber(0));
|
||||||
|
const duoDataIdsForBuHan = Array.from(new Set(
|
||||||
|
projectInvoiceDataInRange
|
||||||
|
.filter(rawItem => rawItem.bu_han_shui_jin_e_yuan_duo !== null && rawItem.bu_han_shui_jin_e_yuan_duo !== undefined && String(rawItem.bu_han_shui_jin_e_yuan_duo).trim() !== '')
|
||||||
|
.map(rawItem => String(rawItem.dataId || ''))
|
||||||
|
.filter(Boolean)
|
||||||
|
));
|
||||||
|
const detailBuHanTotal = duoDataIdsForBuHan.reduce((sum, dataId) => {
|
||||||
|
const key = `${dataId}||${project.project_id || ''}`;
|
||||||
|
const detailHanShuiAmount = detailInvoiceAmountByDataIdProject.get(key) || new BigNumber(0);
|
||||||
|
const sourceRow = projectInvoiceDataInRange.find(rawItem => String(rawItem.dataId || '') === dataId && rawItem.bu_han_shui_jin_e_yuan_duo !== null && rawItem.bu_han_shui_jin_e_yuan_duo !== undefined && String(rawItem.bu_han_shui_jin_e_yuan_duo).trim() !== '');
|
||||||
|
const duoBuHanRaw = String(sourceRow?.bu_han_shui_jin_e_yuan_duo ?? '').replace(/,/g, '').trim();
|
||||||
|
const duoHanRaw = String(sourceRow?.han_shui_jin_e_yuan_duo ?? '').replace(/,/g, '').trim();
|
||||||
|
const duoBuHan = duoBuHanRaw ? new BigNumber(duoBuHanRaw) : new BigNumber(0);
|
||||||
|
const duoHan = duoHanRaw ? new BigNumber(duoHanRaw) : new BigNumber(0);
|
||||||
|
if (duoHan.gt(0) && duoBuHan.gte(0)) {
|
||||||
|
return sum.plus(detailHanShuiAmount.multipliedBy(duoBuHan).dividedBy(duoHan));
|
||||||
|
}
|
||||||
|
return sum.plus(detailHanShuiAmount);
|
||||||
|
}, new BigNumber(0));
|
||||||
|
invoiceExcelData['开票收入(不含税)'] = nonDuoMainBuHanTotal.plus(detailBuHanTotal).decimalPlaces(2, BigNumber.ROUND_HALF_UP);
|
||||||
|
// 开票时间
|
||||||
|
if (invoiceExcelData['开票收入(不含税)'].eq(0)) {
|
||||||
|
invoiceExcelData['开票时间'] = '';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const validInvoiceData = projectInvoiceDataInRange
|
||||||
|
.filter(item => item.kai_piao_ri_qi)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = dayjs(a.kai_piao_ri_qi);
|
||||||
|
const dateB = dayjs(b.kai_piao_ri_qi);
|
||||||
|
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
invoiceExcelData['开票时间'] = validInvoiceData.length > 0 ? validInvoiceData[0].kai_piao_ri_qi ?? '' : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceExcelData['是否交付'] = invoiceExcelData['开票收入(不含税)'].gt(0) ? '是' : '否'
|
||||||
|
const nonDuoMainHanShuiTotal = projectInvoiceDataInRange.reduce((sum, rawItem, idx) => {
|
||||||
|
const duoRaw = rawItem.han_shui_jin_e_yuan_duo;
|
||||||
|
const isDuoMain = duoRaw !== null && duoRaw !== undefined && String(duoRaw).trim() !== '';
|
||||||
|
if (isDuoMain) return sum;
|
||||||
|
const processed = modifiedProjectInvoiceDataInRange[idx];
|
||||||
|
return sum.plus(new BigNumber(processed?.han_shui_zong_jin_e || 0));
|
||||||
|
}, new BigNumber(0));
|
||||||
|
const duoDataIds = Array.from(new Set(
|
||||||
|
projectInvoiceDataInRange
|
||||||
|
.filter(rawItem => rawItem.han_shui_jin_e_yuan_duo !== null && rawItem.han_shui_jin_e_yuan_duo !== undefined && String(rawItem.han_shui_jin_e_yuan_duo).trim() !== '')
|
||||||
|
.map(rawItem => String(rawItem.dataId || ''))
|
||||||
|
.filter(Boolean)
|
||||||
|
));
|
||||||
|
const detailHanShuiTotal = duoDataIds.reduce((sum, dataId) => {
|
||||||
|
const key = `${dataId}||${project.project_id || ''}`;
|
||||||
|
return sum.plus(detailInvoiceAmountByDataIdProject.get(key) || new BigNumber(0));
|
||||||
|
}, new BigNumber(0));
|
||||||
|
invoiceExcelData['开票金额(含税)'] = nonDuoMainHanShuiTotal.plus(detailHanShuiTotal);
|
||||||
|
invoiceExcelData['已回款'] = modifiedProjectInvoiceDataInRange.reduce((sum, item) => (sum).plus(new BigNumber(item.hui_kuan_jin_e || 0)), new BigNumber(0));
|
||||||
|
invoiceExcelData['是否回款'] = invoiceExcelData['已回款'].gt(0) ? '是' : '否';
|
||||||
|
invoiceExcelData['未回款金额'] = new BigNumber(invoiceExcelData['开票金额(含税)']).minus(new BigNumber(invoiceExcelData['已回款']));
|
||||||
|
// invoiceExcelData['回款时间'] = invoiceExcelData['已回款'] === 0 ? '' :
|
||||||
|
// (projectInvoiceData.length > 0 ?
|
||||||
|
// projectInvoiceData.reduce((latest, current) =>
|
||||||
|
// !latest.hui_kuan_ri_qi || new Date(current.hui_kuan_ri_qi) > new Date(latest.hui_kuan_ri_qi) ? current : latest
|
||||||
|
// ).hui_kuan_ri_qi : '');
|
||||||
|
// 回款日期逻辑
|
||||||
|
if (invoiceExcelData['已回款'].eq(0)) {
|
||||||
|
invoiceExcelData['回款时间'] = '';
|
||||||
|
} else {
|
||||||
|
const validInvoiceData = projectInvoiceDataInRange.filter(item => item.hui_kuan_ri_qi);
|
||||||
|
const sortedInvoiceData = validInvoiceData.sort((a, b) => {
|
||||||
|
const dateA = dayjs(a.hui_kuan_ri_qi);
|
||||||
|
const dateB = dayjs(b.hui_kuan_ri_qi);
|
||||||
|
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||||
|
});
|
||||||
|
// 最晚回款时间
|
||||||
|
invoiceExcelData['回款时间'] = sortedInvoiceData.length > 0 ? sortedInvoiceData[sortedInvoiceData.length - 1]?.hui_kuan_ri_qi ?? '' : '';
|
||||||
|
|
||||||
|
}
|
||||||
|
// 实际支出
|
||||||
|
const obj = {
|
||||||
|
'已报销': new BigNumber(0),
|
||||||
|
'SR号': '',
|
||||||
|
'预计未报销金额': new BigNumber(0),
|
||||||
|
'已支付': new BigNumber(0),
|
||||||
|
'预计项目收益': new BigNumber(0),
|
||||||
|
'系统项目收益': new BigNumber(0),
|
||||||
|
'现金流': new BigNumber(0),
|
||||||
|
'价值创造': new BigNumber(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目号一致的费用报销数据
|
||||||
|
const projectExpenseReimbursementData = expenseReimbursementData.filter(item => item.project_number === project.project_id);
|
||||||
|
// 计算已报销金额 =SUMIF(费用报销!C:C,C6,费用报销!G:G)
|
||||||
|
obj['已报销'] = projectExpenseReimbursementData.reduce((sum, item) => (sum).plus(new BigNumber(item.amount || 0)), new BigNumber(0));
|
||||||
|
|
||||||
|
// 如果已报销是0 那么空字符串 如果不是0 那么找到第一个SR号
|
||||||
|
if (obj['已报销'].eq(0)) {
|
||||||
|
obj['SR号'] = '';
|
||||||
|
} else {
|
||||||
|
// 查找第一个有SR号的记录
|
||||||
|
const firstRecordWithSR = projectExpenseReimbursementData.find(item => item.sr_number && item.sr_number.trim() !== '');
|
||||||
|
obj['SR号'] = firstRecordWithSR ? firstRecordWithSR.sr_number : '';
|
||||||
|
}
|
||||||
|
// 预计未报销金额 = 预计总支出 - 已报销
|
||||||
|
obj['预计未报销金额'] = new BigNumber(invoiceExcelData['预计总支出 (不含税)']).minus(new BigNumber(obj['已报销']));
|
||||||
|
// 汇总实际支付表内的期末余额
|
||||||
|
obj['已支付'] = actualPaymentData.filter(item => item.project_number === project.project_id).reduce((sum, item) => (sum).plus(new BigNumber(item.qimo_yueyuan || 0)), new BigNumber(0));
|
||||||
|
|
||||||
|
// 开票收入 - 预计总支出
|
||||||
|
obj['预计项目收益'] = new BigNumber(invoiceExcelData['开票收入(不含税)']).minus(new BigNumber(invoiceExcelData['预计总支出 (不含税)']));
|
||||||
|
// 开票收入 - 已报销
|
||||||
|
obj['系统项目收益'] = new BigNumber(invoiceExcelData['开票收入(不含税)']).minus(new BigNumber(obj['已报销']));
|
||||||
|
// -
|
||||||
|
obj['现金流'] = new BigNumber(invoiceExcelData['已回款']).minus(new BigNumber(obj['已支付']));
|
||||||
|
// 价值创造 = 0.54 * 开票金额(含税)
|
||||||
|
obj['价值创造'] = new BigNumber(invoiceExcelData['开票金额(含税)']).times(new BigNumber(0.54));
|
||||||
|
|
||||||
|
// 对
|
||||||
|
const merged = {
|
||||||
|
...data, ...sales_contract, ...purchase_contract, ...invoiceExcelData, ...obj, salesContracts: salesContractsObj, purchaseContractsList, modifiedProjectInvoiceData: modifiedProjectInvoiceDataInRange
|
||||||
|
};
|
||||||
|
const finalObj = processFinalObject(merged);
|
||||||
|
excelDatas.push(finalObj)
|
||||||
|
})
|
||||||
|
return excelDatas;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFinalObject(
|
||||||
|
merged: Record<string, any>,
|
||||||
|
): Record<string, any> {
|
||||||
|
for (const key in merged) {
|
||||||
|
if (merged.hasOwnProperty(key)) {
|
||||||
|
const value = merged[key];
|
||||||
|
if (BigNumber.isBigNumber(value)) {
|
||||||
|
merged[key] = value.toString();
|
||||||
|
} else {
|
||||||
|
merged[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
382
src/pages/api/project/utilCopy.ts
Normal file
382
src/pages/api/project/utilCopy.ts
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { XIANGMU_ZUBIE_MAP, ABBR_ZUBIE_MAP } from '@/CONSTANTS'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
|
|
||||||
|
export async function getBussinessDetailsList(projects) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const businessContentMap = new Map<string, string[]>();// <-- 一个空对象为了复用函数
|
||||||
|
// 1 拿到所有的项目id
|
||||||
|
const projectNumbers = projects.map(project => project.project_id).filter(Boolean);
|
||||||
|
// 2 先拉取发票数据用于建立项目号与销售合同号映射
|
||||||
|
|
||||||
|
|
||||||
|
// 根据项目号 从 invoice_application_match_view 中拿到开票信息(排除作废)
|
||||||
|
const voidRecords = await prismaClient.dingding_oa_invoice_application.findMany({
|
||||||
|
where: { remark: '发票作废' },
|
||||||
|
select: { dataId: true }
|
||||||
|
});
|
||||||
|
const voidIds = voidRecords.map(r => r.dataId).filter(Boolean);
|
||||||
|
const invoiceData = await prismaClient.invoice_application_match_view.findMany({
|
||||||
|
where: {
|
||||||
|
OR: projectNumbers.map(p => ({ projectNumber: { contains: p } })),
|
||||||
|
...(voidIds.length > 0 ? { dataId: { notIn: voidIds } } : {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从开票数据中提取项目号和销售合同编号
|
||||||
|
const salesContractToProjectMap: {
|
||||||
|
[k: string]: string;
|
||||||
|
} = {};
|
||||||
|
invoiceData.forEach(item => {
|
||||||
|
if (item.salesContractNumber && item.projectNumber) {
|
||||||
|
salesContractToProjectMap[item.salesContractNumber] = item.projectNumber;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const invoiceDataSalesContractNumbers = invoiceData.map(item => item.salesContractNumber).filter(ele => ele);
|
||||||
|
|
||||||
|
// 3 根据项目编号或关联销售合同编号查采购订单
|
||||||
|
const purchaseContractsOrder = await prismaClient.purchase_contract_order_management.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建销售合同编号到项目编号的映射(从采购订单数据中获取)
|
||||||
|
const salesContractToProjectMapFromPurchase = new Map<string, string>();
|
||||||
|
|
||||||
|
// 为每个项目单独查询相关的销售合同
|
||||||
|
const salesContractsWithProjectNumbers = [];
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
const projectNumber = project.project_id;
|
||||||
|
if (!projectNumber) continue;
|
||||||
|
|
||||||
|
// 根据项目编号从采购订单表中获取相关的销售合同编号
|
||||||
|
const purchaseContractsWithSalesContract = await prismaClient.purchase_contract_order_management.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
contains: projectNumber
|
||||||
|
},
|
||||||
|
associated_sales_contract_number: {
|
||||||
|
not: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
associated_sales_contract_number: true,
|
||||||
|
project_number: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提取当前项目相关的销售合同编号
|
||||||
|
const salesContractNumbersForProject = purchaseContractsWithSalesContract
|
||||||
|
.map(item => item.associated_sales_contract_number)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (salesContractNumbersForProject.length === 0) continue;
|
||||||
|
|
||||||
|
// 根据销售合同编号在procurement_contract_list表中筛选出当前项目的销售合同
|
||||||
|
const salesContractsForProject = await prismaClient.procurement_contract_list.findMany({
|
||||||
|
where: {
|
||||||
|
contract_number: {
|
||||||
|
in: salesContractNumbersForProject
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新映射关系
|
||||||
|
purchaseContractsWithSalesContract.forEach(item => {
|
||||||
|
if (item.associated_sales_contract_number && item.project_number) {
|
||||||
|
salesContractToProjectMapFromPurchase.set(item.associated_sales_contract_number, item.project_number);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将当前项目的销售合同添加到结果数组
|
||||||
|
const salesContractsForProjectWithNumbers = salesContractsForProject.map(contract => {
|
||||||
|
const projectNumberFromPurchase = salesContractToProjectMapFromPurchase.get(contract?.contract_number || '');
|
||||||
|
const projectNumberFromInvoice = salesContractToProjectMap[contract?.contract_number] || null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...contract,
|
||||||
|
project_number: projectNumberFromPurchase || projectNumberFromInvoice || projectNumber
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
salesContractsWithProjectNumbers.push(...salesContractsForProjectWithNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据项目号 从 expense_reimbursement 中拿到费用报销信息
|
||||||
|
const expenseReimbursementData = await prismaClient.expense_reimbursement.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从 实际支付表内拿到项目号 对应的 期末余额
|
||||||
|
const actualPaymentData = await prismaClient.actual_payment.findMany({
|
||||||
|
where: {
|
||||||
|
project_number: {
|
||||||
|
in: projectNumbers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 1 提取出 projects 表内所有的client_name 之后去 customer_management_info内提取对应的 customer_type 形成KV映射
|
||||||
|
const customerTypeMap = new Map<string, string>();
|
||||||
|
const customerNames = projects.map(project => project.client_name).filter(Boolean);
|
||||||
|
const customerManagementInfo = await prismaClient.customer_management_info.findMany({
|
||||||
|
where: {
|
||||||
|
customer_unit_name: {
|
||||||
|
in: customerNames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
customerManagementInfo.forEach(item => {
|
||||||
|
customerTypeMap.set(item.customer_unit_name, item.customer_type || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// excelDatas 对象存储每一行的数据
|
||||||
|
const excelDatas = []
|
||||||
|
projects.map((project, idx) => {
|
||||||
|
// 专业组逻辑:提取项目编号的所有字母部分映射,如果没有项目编号则为客户服务室
|
||||||
|
const getProjectGroupByProjectId = (projectId: string | null | undefined): string => {
|
||||||
|
if (!projectId) {
|
||||||
|
return '客户服务室';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目编号的所有字母部分(匹配连续的字母字符,不限位数)
|
||||||
|
const letterMatch = projectId.match(/^[A-Za-z]+/);
|
||||||
|
const prefix = letterMatch ? letterMatch[0] : '';
|
||||||
|
|
||||||
|
// 根据字母前缀映射专业组
|
||||||
|
const groupMapping: { [key: string]: string } = {
|
||||||
|
'YY': '运营室',
|
||||||
|
'ZP': '人员招聘项目组',
|
||||||
|
'ZW': '总务服务项目组',
|
||||||
|
'XC': '薪酬核算项目组',
|
||||||
|
'TX': '退休衔接服务项目组',
|
||||||
|
'TC': '统筹支持项目组',
|
||||||
|
'RZL': '人员入转离项目组',
|
||||||
|
'PJ': '人才评鉴项目组',
|
||||||
|
'HW': '会务服务项目组',
|
||||||
|
'GJ': '国际化服务项目组',
|
||||||
|
'GZ': '管理咨询项目组',
|
||||||
|
'KF': '客户服务室',
|
||||||
|
'FL': '福利业务项目组',
|
||||||
|
'FW': '法务合规中心',
|
||||||
|
'SJ': '数字化与交付项目组',
|
||||||
|
'KJ': '科技培训项目组',
|
||||||
|
'JG': '经营管理项目组',
|
||||||
|
'JN': '技能培训项目组',
|
||||||
|
'DJ': '党建培训项目组',
|
||||||
|
'JH': '计划核定项目组',
|
||||||
|
'LGB': '老干部服务项目组'
|
||||||
|
};
|
||||||
|
|
||||||
|
return groupMapping[prefix] || '客户服务室';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 逻辑以 excel 为标准逻辑
|
||||||
|
// 1 项目类
|
||||||
|
const data = {
|
||||||
|
'序号': idx + 1,
|
||||||
|
'项目编号': project.project_id,
|
||||||
|
'项目名称': project.project_name,
|
||||||
|
'客户名称': project.client_name,
|
||||||
|
'交付区域': project.delivery_area,
|
||||||
|
'单位性质': customerTypeMap.get(project.client_name || '') || '', //
|
||||||
|
'项目主担': project.project_leader,
|
||||||
|
'项目组别': getProjectGroupByProjectId(project.project_id),
|
||||||
|
// '项目组别': XIANGMU_ZUBIE_MAP[ABBR_ZUBIE_MAP[project.group_abbreviation]], //
|
||||||
|
}
|
||||||
|
// 2 合同大类
|
||||||
|
// 销售合同 采购合同
|
||||||
|
// 销售合同/订单数量 =COUNTIFS(合同管理!D:D,C6,合同管理!C:C,$X$2)
|
||||||
|
// 销售合同号 =IF(X6=1,VLOOKUP($X$2&C6,合同管理!A:E,5,0),"-")
|
||||||
|
// 销售合同金额 =SUMIFS(合同管理!F:F,合同管理!D:D,C6,合同管理!C:C,$X$2)
|
||||||
|
// 客户名称 =IF(X6=1,VLOOKUP($X$2&C6,合同管理!A:H,8,0),"-")
|
||||||
|
//
|
||||||
|
// 采购合同/订单数量 =COUNTIFS(合同管理!D:D,C6,合同管理!C:C,$AB$2)
|
||||||
|
// 采购合同号 =IF(AB6=1,VLOOKUP(C6,合同管理!D:E,2,0),"-")
|
||||||
|
// 采购合同金额 =SUMIFS(合同管理!F:F,合同管理!D:D,C6,合同管理!C:C,$AB$2)
|
||||||
|
// 供应商名称 =IF(AB6=1,VLOOKUP($AB$2&C6,合同管理!A:H,8,0),"-")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const sales_contract = {
|
||||||
|
'销售订单数量': 0,//
|
||||||
|
// '销售合同号': '-',
|
||||||
|
'销售合同金额': new BigNumber(0),// string 或者 bignumber
|
||||||
|
// '客户名称': '-',
|
||||||
|
}
|
||||||
|
// 销售订单数量 这个变量等于 salesContractsWithProjectNumbers 里面项目号一致的合同的数量
|
||||||
|
const salesContractsObj = salesContractsWithProjectNumbers.filter(item => (item.project_number || '').includes(project.project_id));
|
||||||
|
sales_contract['销售订单数量'] = salesContractsObj.length
|
||||||
|
// sales_contract['销售合同号'] = salesContractsObj.length === 1 ? salesContractsObj[0].contract_name || '-' : '-';
|
||||||
|
sales_contract['销售合同金额'] = salesContractsObj.length > 0 ?
|
||||||
|
salesContractsObj.reduce((sum, contract) => (sum).plus(new BigNumber(contract?.contract_amount || 0)), new BigNumber(0)) : new BigNumber(0);
|
||||||
|
// sales_contract['客户名称'] = salesContractsObj.length === 1 ? salesContractsObj[0].xiangduifang || '-' : '-';
|
||||||
|
|
||||||
|
const purchase_contract = {
|
||||||
|
'采购订单数量': 0,
|
||||||
|
// '采购合同号': '-',
|
||||||
|
'采购合同金额': new BigNumber(0),
|
||||||
|
// '供应商名称': '-',
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// 采购订单数量 这个变量等于 purchaseContractsOrder 里面项目号一致的合同的数量
|
||||||
|
const purchaseContractsList = purchaseContractsOrder.filter(item => item.project_number === project.project_id);
|
||||||
|
// 根据合同号去重
|
||||||
|
const purchaseContractsListDeduplication = purchaseContractsList.reduce((acc, current) => {
|
||||||
|
const existing = acc.find(item => item.associated_sales_contract_number === current.associated_sales_contract_number);
|
||||||
|
if (!existing) {
|
||||||
|
acc.push(current);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
purchase_contract['采购订单数量'] = purchaseContractsList.length
|
||||||
|
// purchase_contract['采购合同号'] = purchaseContractsList.length === 1 ? purchaseContractsObj[0].contract_number || '-' : '-';
|
||||||
|
purchase_contract['采购合同金额'] = purchaseContractsListDeduplication.length > 0 ?
|
||||||
|
purchaseContractsListDeduplication.reduce((sum, contract) => (sum).plus(new BigNumber(contract?.he_tong_zong_jin_e_HanShui || 0)), new BigNumber(0)) : new BigNumber(0);
|
||||||
|
// purchase_contract['供应商名称'] = purchaseContractsList.length === 1 ? purchaseContractsObj[0].xiangduifang || '-' : '-';
|
||||||
|
|
||||||
|
|
||||||
|
// 提取项目号一致的开票数据
|
||||||
|
const projectInvoiceData = invoiceData.filter(item => (item.projectNumber || '').includes(project.project_id));
|
||||||
|
|
||||||
|
const modifiedProjectInvoiceData = projectInvoiceData.map((ele) => processInvoiceDataItem(ele, businessContentMap));
|
||||||
|
// 接下来是金额组
|
||||||
|
// "预计总支出 (不含税)" 开票时间 " 开票收入(不含税) " 是否交付 " 划入交付组收入 " " 开票金额(含税) " 已回款 是否回款 未回款金额 回款时间
|
||||||
|
const invoiceExcelData = {
|
||||||
|
'预计总支出 (不含税)': new BigNumber(0),
|
||||||
|
'开票时间': '-', // =IF(AI74=0," ",VLOOKUP(C74,'发票台账23-25'!B:K,10,0))
|
||||||
|
'开票收入(不含税)': new BigNumber(0),
|
||||||
|
'是否交付': '-',
|
||||||
|
'划入交付组收入': '待定TODO',
|
||||||
|
'开票金额(含税)': new BigNumber(0),
|
||||||
|
'已回款': new BigNumber(0),
|
||||||
|
'是否回款': '-',
|
||||||
|
'未回款金额': new BigNumber(0),
|
||||||
|
'回款时间': '-',
|
||||||
|
}
|
||||||
|
// modifiedProjectInvoiceData 是为了拿到 税额因为他们要判断类型很复杂
|
||||||
|
// 预计总支出= 所有的预计支付第三方成本的和
|
||||||
|
const uniqueExpenseAmounts = Array.from(new Set(projectInvoiceData.map(item => new BigNumber(item.yuJiBaoXiaoDiSanFangChengBenYuan || 0).toString())));
|
||||||
|
invoiceExcelData['预计总支出 (不含税)'] = uniqueExpenseAmounts.reduce((sum, amt) => (sum).plus(new BigNumber(amt)), new BigNumber(0));
|
||||||
|
// 开票收入不函数 == SUMIF('发票台账23-25'!B:B,C21,'发票台账23-25'!F:F)
|
||||||
|
invoiceExcelData['开票收入(不含税)'] = modifiedProjectInvoiceData.reduce((sum, item) => (sum).plus(new BigNumber(item.bu_han_shui_jin_e || 0)), new BigNumber(0));
|
||||||
|
// 开票时间
|
||||||
|
if (invoiceExcelData['开票收入(不含税)'].eq(0)) {
|
||||||
|
invoiceExcelData['开票时间'] = '';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const validInvoiceData = projectInvoiceData
|
||||||
|
.filter(item => item.kai_piao_ri_qi)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const dateA = dayjs(a.kai_piao_ri_qi);
|
||||||
|
const dateB = dayjs(b.kai_piao_ri_qi);
|
||||||
|
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
invoiceExcelData['开票时间'] = validInvoiceData.length > 0 ? validInvoiceData[0].kai_piao_ri_qi ?? '' : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceExcelData['是否交付'] = invoiceExcelData['开票收入(不含税)'].gt(0) ? '是' : '否'
|
||||||
|
invoiceExcelData['开票金额(含税)'] = modifiedProjectInvoiceData.reduce((sum, item) => (sum).plus(new BigNumber(item.han_shui_zong_jin_e || 0)), new BigNumber(0));
|
||||||
|
invoiceExcelData['已回款'] = modifiedProjectInvoiceData.reduce((sum, item) => (sum).plus(new BigNumber(item.hui_kuan_jin_e || 0)), new BigNumber(0));
|
||||||
|
invoiceExcelData['是否回款'] = invoiceExcelData['已回款'].gt(0) ? '是' : '否';
|
||||||
|
invoiceExcelData['未回款金额'] = new BigNumber(invoiceExcelData['开票金额(含税)']).minus(new BigNumber(invoiceExcelData['已回款']));
|
||||||
|
// invoiceExcelData['回款时间'] = invoiceExcelData['已回款'] === 0 ? '' :
|
||||||
|
// (projectInvoiceData.length > 0 ?
|
||||||
|
// projectInvoiceData.reduce((latest, current) =>
|
||||||
|
// !latest.hui_kuan_ri_qi || new Date(current.hui_kuan_ri_qi) > new Date(latest.hui_kuan_ri_qi) ? current : latest
|
||||||
|
// ).hui_kuan_ri_qi : '');
|
||||||
|
// 回款日期逻辑
|
||||||
|
if (invoiceExcelData['已回款'].eq(0)) {
|
||||||
|
invoiceExcelData['回款时间'] = '';
|
||||||
|
} else {
|
||||||
|
const validInvoiceData = projectInvoiceData.filter(item => item.hui_kuan_ri_qi);
|
||||||
|
const sortedInvoiceData = validInvoiceData.sort((a, b) => {
|
||||||
|
const dateA = dayjs(a.hui_kuan_ri_qi);
|
||||||
|
const dateB = dayjs(b.hui_kuan_ri_qi);
|
||||||
|
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
|
||||||
|
});
|
||||||
|
// 最晚回款时间
|
||||||
|
invoiceExcelData['回款时间'] = sortedInvoiceData.length > 0 ? sortedInvoiceData[sortedInvoiceData.length - 1]?.hui_kuan_ri_qi ?? '' : '';
|
||||||
|
|
||||||
|
}
|
||||||
|
// 实际支出
|
||||||
|
const obj = {
|
||||||
|
'已报销': new BigNumber(0),
|
||||||
|
'SR号': '',
|
||||||
|
'预计未报销金额': new BigNumber(0),
|
||||||
|
'已支付': new BigNumber(0),
|
||||||
|
'预计项目收益': new BigNumber(0),
|
||||||
|
'系统项目收益': new BigNumber(0),
|
||||||
|
'现金流': new BigNumber(0),
|
||||||
|
'价值创造': new BigNumber(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取项目号一致的费用报销数据
|
||||||
|
const projectExpenseReimbursementData = expenseReimbursementData.filter(item => item.project_number === project.project_id);
|
||||||
|
// 计算已报销金额 =SUMIF(费用报销!C:C,C6,费用报销!G:G)
|
||||||
|
obj['已报销'] = projectExpenseReimbursementData.reduce((sum, item) => (sum).plus(new BigNumber(item.amount || 0)), new BigNumber(0));
|
||||||
|
|
||||||
|
// 如果已报销是0 那么空字符串 如果不是0 那么找到第一个SR号
|
||||||
|
if (obj['已报销'].eq(0)) {
|
||||||
|
obj['SR号'] = '';
|
||||||
|
} else {
|
||||||
|
// 查找第一个有SR号的记录
|
||||||
|
const firstRecordWithSR = projectExpenseReimbursementData.find(item => item.sr_number && item.sr_number.trim() !== '');
|
||||||
|
obj['SR号'] = firstRecordWithSR ? firstRecordWithSR.sr_number : '';
|
||||||
|
}
|
||||||
|
// 预计未报销金额 = 预计总支出 - 已报销
|
||||||
|
obj['预计未报销金额'] = new BigNumber(invoiceExcelData['预计总支出 (不含税)']).minus(new BigNumber(obj['已报销']));
|
||||||
|
// 汇总实际支付表内的期末余额
|
||||||
|
obj['已支付'] = actualPaymentData.filter(item => item.project_number === project.project_id).reduce((sum, item) => (sum).plus(new BigNumber(item.qimo_yueyuan || 0)), new BigNumber(0));
|
||||||
|
|
||||||
|
// 开票收入 - 预计总支出
|
||||||
|
obj['预计项目收益'] = new BigNumber(invoiceExcelData['开票收入(不含税)']).minus(new BigNumber(invoiceExcelData['预计总支出 (不含税)']));
|
||||||
|
// 开票收入 - 已报销
|
||||||
|
obj['系统项目收益'] = new BigNumber(invoiceExcelData['开票收入(不含税)']).minus(new BigNumber(obj['已报销']));
|
||||||
|
// -
|
||||||
|
obj['现金流'] = new BigNumber(invoiceExcelData['已回款']).minus(new BigNumber(obj['已支付']));
|
||||||
|
// 价值创造 = 0.54 * 开票金额(含税)
|
||||||
|
obj['价值创造'] = new BigNumber(invoiceExcelData['开票金额(含税)']).times(new BigNumber(0.54));
|
||||||
|
|
||||||
|
// 对
|
||||||
|
const merged = {
|
||||||
|
...data, ...sales_contract, ...purchase_contract, ...invoiceExcelData, ...obj, salesContracts, purchaseContractsList, modifiedProjectInvoiceData
|
||||||
|
};
|
||||||
|
const finalObj = processFinalObject(merged);
|
||||||
|
excelDatas.push(finalObj)
|
||||||
|
})
|
||||||
|
return excelDatas;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFinalObject(
|
||||||
|
merged: Record<string, any>,
|
||||||
|
): Record<string, any> {
|
||||||
|
for (const key in merged) {
|
||||||
|
if (merged.hasOwnProperty(key)) {
|
||||||
|
const value = merged[key];
|
||||||
|
if (BigNumber.isBigNumber(value)) {
|
||||||
|
merged[key] = value.toString();
|
||||||
|
} else {
|
||||||
|
merged[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import { PurchaseContractExecutionMaintenanceItem } from '../type';
|
||||||
|
|
||||||
|
const ExcelToMySQLFieldMapping = {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'发起人': 'initiator',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'合同编号': 'contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'履约发起部门': 'lvyue_faqi_bumen',
|
||||||
|
'经办人': 'operator',
|
||||||
|
'相对方': 'xiangduifang',
|
||||||
|
'是否开口': 'is_open',
|
||||||
|
'合同总额': 'contract_amount',
|
||||||
|
'备注': 'remarks',
|
||||||
|
'合同余额': 'contract_balance',
|
||||||
|
'履约开始时间': 'performance_start_time',
|
||||||
|
'合同原件扫描件(盖章版)': 'hetong_yuanjian_saomiaojian_gaizhangban',
|
||||||
|
'钉钉采购审批单': 'dingtalk_procurement_approval',
|
||||||
|
'合同附件扫描件': 'contract_attachment_scan',
|
||||||
|
'数据库索引': 'database_index',
|
||||||
|
'审批编号': 'approval_number',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'current_responsible',
|
||||||
|
'审批结果': 'approval_result',
|
||||||
|
'审批状态': 'approval_status',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'历史审批人': 'historical_approvers',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
const projectDataList:PurchaseContractExecutionMaintenanceItem[] = [];
|
||||||
|
// TODO 可以查询一次搞重新修改 看业务需求
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const projectData: any = {
|
||||||
|
data_id: rawItem['数据id'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
projectData[dbKey] = rawItem[excelKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
projectDataList.push(projectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据到purchase_contract_execution_maintenance表
|
||||||
|
await prismaClient.purchase_contract_execution_maintenance.createMany({
|
||||||
|
data: projectDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: '采购合同履约数据导入成功'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
90
src/pages/api/purchase_contract_order_management/import.ts
Normal file
90
src/pages/api/purchase_contract_order_management/import.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { withFileUpload } from 'next-multiparty';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { PurchaseContractOrderManagementItem, PurchaseContractOrderManagementDBItem } from '../type'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
const ExcelToMySQLFieldMapping = {
|
||||||
|
'数据id': 'data_id',
|
||||||
|
'采购合同编号': 'purchase_contract_number',
|
||||||
|
'采购用途': 'purchase_purpose',
|
||||||
|
'项目编号': 'project_number',
|
||||||
|
'关联销售合同编号': 'associated_sales_contract_number',
|
||||||
|
'合同名称': 'contract_name',
|
||||||
|
'采购审批单号(请先填写合同编号)': 'purchase_approval_number',
|
||||||
|
'采购合同名称(***请再次确认合同名称***)': 'purchase_contract_name',
|
||||||
|
'相对方': 'xiang_dui_fang',
|
||||||
|
'合同类别(是否开口)': 'he_tong_lei_bie',
|
||||||
|
'合同总金额(含税)': 'he_tong_zong_jin_e_HanShui',
|
||||||
|
'剩余金额(含税)': 'sheng_yu_jin_e_HanShui',
|
||||||
|
'合同剩余金额提醒': 'he_tong_sheng_yu_jin_e_ti_xing',
|
||||||
|
'本次订单名称': 'ben_ci_ding_dan_ming_cheng',
|
||||||
|
'本次订单金额(含税)': 'ben_ci_ding_dan_jin_e_HanShui',
|
||||||
|
'本次订单履约开始时间': 'ben_ci_ding_dan_lu_yue_kai_shi_shi_jian',
|
||||||
|
'本次订单履约结束时间': 'ben_ci_ding_dan_lu_yue_jie_shu_shi_jian',
|
||||||
|
'本次订单发起部门': 'ben_ci_ding_dan_fa_qi_bu_men',
|
||||||
|
'本次订单发起人': 'ben_ci_ding_dan_fa_qi_ren',
|
||||||
|
'本次订单发起时间': 'ben_ci_ding_dan_fa_qi_shi_jian',
|
||||||
|
'审批编号': 'approval_number',
|
||||||
|
'创建时间': 'creation_time',
|
||||||
|
'创建人': 'creator',
|
||||||
|
'当前负责人': 'current_responsible_person',
|
||||||
|
'审批结果': 'approval_result',
|
||||||
|
'审批状态': 'approval_status',
|
||||||
|
'更新时间': 'update_time',
|
||||||
|
'创建人部门': 'creator_department',
|
||||||
|
'审批单标题': 'approval_form_title',
|
||||||
|
'耗时(时:分:秒)': 'time_consumed'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false // 必须禁用默认bodyParser
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withFileUpload(async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
}
|
||||||
|
// 2. 读取Excel文件
|
||||||
|
const fileBuffer = await req?.file?.toBuffer()
|
||||||
|
const workbook = XLSX.read(fileBuffer, { type: 'buffer' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const rawData: PurchaseContractOrderManagementItem[] = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
// 3. 数据处理和去重
|
||||||
|
const projectDataList: PurchaseContractOrderManagementDBItem[] = [];
|
||||||
|
// TODO 可以查询一次搞重新修改 看业务需求
|
||||||
|
for (let i = 0; i < rawData.length; i++) {
|
||||||
|
const rawItem = rawData[i];
|
||||||
|
const projectData: any = {
|
||||||
|
data_id: rawItem['数据id'],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [excelKey, dbKey] of Object.entries(ExcelToMySQLFieldMapping)) {
|
||||||
|
const val = rawItem[excelKey];
|
||||||
|
const str = val === undefined || val === null ? '' : String(val).trim();
|
||||||
|
projectData[dbKey] = str === '' ? null : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
projectDataList.push(projectData as PurchaseContractOrderManagementDBItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入数据到purchase_contract_order_management表
|
||||||
|
await prismaClient.purchase_contract_order_management.createMany({
|
||||||
|
data: projectDataList,
|
||||||
|
skipDuplicates: true, // 跳过重复数据
|
||||||
|
});
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
data: null,
|
||||||
|
message: '采购合同订单管理数据导入成功'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Internal server error', stack: error, });
|
||||||
|
}
|
||||||
|
})
|
||||||
158
src/pages/api/receivable_accounts/get.ts
Normal file
158
src/pages/api/receivable_accounts/get.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* @description 这里是应收账款台账表格
|
||||||
|
* 参考的拉取开票信息的逻辑
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { AGREEMENT_TYPES, AGREEMENT_TYPE_NAME, AGREEMENT_TYPE_CODE } from '@/CONSTANTS'
|
||||||
|
import type { InvoiceDataItem, ProcessedInvoiceDataItem } from '@/pages/api/type'
|
||||||
|
import { processInvoiceDataItem } from '@/lib/util'
|
||||||
|
import { processReceivableAccounts } from '@/pages/api/receivable_accounts/util'
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data?: {
|
||||||
|
data: any;
|
||||||
|
pagination?: {
|
||||||
|
total: number
|
||||||
|
pageNum: number
|
||||||
|
pageSize: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
error?: any
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (req.method != 'POST') {
|
||||||
|
return res.status(400).json({ message: '请求方法错误' });
|
||||||
|
}
|
||||||
|
// 从查询参数获取分页信息,设置默认值
|
||||||
|
const pageNum = parseInt(req.body.pageNum as string) || 1;
|
||||||
|
const pageSize = parseInt(req.body.pageSize as string) || 10;
|
||||||
|
const skip = (pageNum - 1) * pageSize;
|
||||||
|
|
||||||
|
const { agreementType, projectNumber, customerCompanyName, joinType = 'left' } = req.body;
|
||||||
|
const whereConditions: any = {};
|
||||||
|
if (agreementType) {
|
||||||
|
whereConditions.agreementType = agreementType as string;
|
||||||
|
}
|
||||||
|
if (projectNumber) {
|
||||||
|
whereConditions.projectNumber = {
|
||||||
|
contains: projectNumber as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (customerCompanyName) {
|
||||||
|
whereConditions.customerCompanyName = {
|
||||||
|
contains: customerCompanyName as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(pageNum) || isNaN(pageSize) || pageNum < 1 || pageSize < 1) {
|
||||||
|
return res.status(400).json({ code: 400, message: '无效的分页参数' });
|
||||||
|
}
|
||||||
|
// 如果是inner 那么是完成开票结果表,否则是全量数据
|
||||||
|
const viewModel = prismaClient.invoice_application_match_view
|
||||||
|
// 获取分页数据和总记录数
|
||||||
|
const [invoiceData, total] = await Promise.all([
|
||||||
|
viewModel.findMany({
|
||||||
|
where: whereConditions,
|
||||||
|
skip,
|
||||||
|
take: pageSize,
|
||||||
|
orderBy: {
|
||||||
|
projectNumber: 'asc',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
viewModel.count({
|
||||||
|
where: whereConditions
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 提取大协议的dataId
|
||||||
|
const largeAgreementIds = invoiceData
|
||||||
|
.filter(item => item.agreementType === '1')
|
||||||
|
.map(item => item.dataId);
|
||||||
|
|
||||||
|
// 从invoice_amount_details获取大协议的业务内容
|
||||||
|
const largeAgreementDetails = await prismaClient.invoice_amount_details.findMany({
|
||||||
|
where: {
|
||||||
|
dataid: {
|
||||||
|
in: largeAgreementIds
|
||||||
|
},
|
||||||
|
agreementType: AGREEMENT_TYPE_CODE[AGREEMENT_TYPES.LARGE]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建一个映射,存储每个dataid对应的多个business_content
|
||||||
|
// 用于快速获取大协议的业务内容字段
|
||||||
|
const businessContentMap = new Map<string, string[]>();
|
||||||
|
largeAgreementDetails.forEach((detail, idx) => {
|
||||||
|
if (detail.dataid && detail.business_content) {
|
||||||
|
if (!businessContentMap.has(detail.dataid)) {
|
||||||
|
businessContentMap.set(detail.dataid, []);
|
||||||
|
}
|
||||||
|
businessContentMap.get(detail.dataid)?.push(`${idx}. ${detail.project_name},${detail.business_content}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在这里增加逻辑
|
||||||
|
// 1 增加 customerCompanyName 取自 invoiceData.customerCompanyNameLargeAgreement(如果协议类型是大协议 和 invoiceData.customerCompanyNameIndependentAgreement 如果协议类型是独立协议
|
||||||
|
// 2 账期 invoice_period 是 开票日期和回款日期两个字段都存在 那么账期= 回款日期 payment_date -开票日期 invoice_date
|
||||||
|
// 根据项目编号,将project表拉取过来
|
||||||
|
const projectNumberList = invoiceData.map((ele) => ele.projectNumber);
|
||||||
|
// 过滤空值,确保传入 Prisma 的 in 参数合法
|
||||||
|
const projectIdList = projectNumberList
|
||||||
|
.filter((v): v is string => typeof v === 'string' && v.trim() !== '');
|
||||||
|
|
||||||
|
// 如果过滤后为空,跳过数据库查询
|
||||||
|
const projectList = projectIdList.length > 0
|
||||||
|
? await prismaClient.project.findMany({
|
||||||
|
where: {
|
||||||
|
project_id: {
|
||||||
|
in: projectIdList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// 构建 Map 时过滤掉空的 project_id
|
||||||
|
const projectMap = new Map(
|
||||||
|
projectList
|
||||||
|
.filter((ele) => typeof ele.project_id === 'string' && (ele.project_id as string).trim() !== '')
|
||||||
|
.map((ele) => [ele.project_id as string, ele])
|
||||||
|
);
|
||||||
|
|
||||||
|
const modifiedInvoiceData = invoiceData
|
||||||
|
.map((ele) => processInvoiceDataItem(ele, businessContentMap))
|
||||||
|
.map(ele => processReceivableAccounts(ele, projectMap));
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
code: 0,
|
||||||
|
error: null,
|
||||||
|
message: '数据获取成功',
|
||||||
|
data: {
|
||||||
|
data: modifiedInvoiceData,
|
||||||
|
pagination: {
|
||||||
|
total,
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
totalPages: Math.ceil(total / pageSize)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('handler:', error);
|
||||||
|
res.status(500).json({ code: 500, message: '获取数据时出错' });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
48
src/pages/api/receivable_accounts/util.ts
Normal file
48
src/pages/api/receivable_accounts/util.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { ProcessedInvoiceDataItemFromFunction } from '@/pages/api/type'
|
||||||
|
|
||||||
|
export const departmentList = [
|
||||||
|
"运营",
|
||||||
|
"招聘",
|
||||||
|
"总务",
|
||||||
|
"薪酬",
|
||||||
|
"退休衔接",
|
||||||
|
"统筹-计核",
|
||||||
|
"统筹-社保",
|
||||||
|
"统筹-医保",
|
||||||
|
"统筹-支持",
|
||||||
|
"入转离",
|
||||||
|
"评鉴",
|
||||||
|
"培训-会务",
|
||||||
|
"国际化服务",
|
||||||
|
"管理咨询",
|
||||||
|
"客户服务",
|
||||||
|
"福利",
|
||||||
|
"法务",
|
||||||
|
"培训-数交",
|
||||||
|
"培训-科技",
|
||||||
|
"培训-经管",
|
||||||
|
"培训-技能",
|
||||||
|
"党校-党建",
|
||||||
|
"数字化"
|
||||||
|
];
|
||||||
|
|
||||||
|
export function processReceivableAccounts(invoiceData: ProcessedInvoiceDataItemFromFunction, projectMap: Map<string, any>) {
|
||||||
|
// const projectInfo = projectMap.get(invoiceData.projectNumber)
|
||||||
|
const obj: {
|
||||||
|
[k: string]: any
|
||||||
|
} = {
|
||||||
|
...invoiceData,
|
||||||
|
// delivery_area: projectInfo?.delivery_area,
|
||||||
|
// project_group: obj?.zhuan_ye_zu,
|
||||||
|
// 将departmentList中的每个部门作为key,值为空字符串
|
||||||
|
...Object.fromEntries(departmentList.map(department => [department, ""]))
|
||||||
|
}
|
||||||
|
// delivery_area 交付区域 对应负责地区
|
||||||
|
// project_group 项目组别 申请口
|
||||||
|
// departmentList
|
||||||
|
// 如果申请口在 departmentList 内,那么将值设置为申请口
|
||||||
|
if (departmentList.includes(obj.zhuan_ye_zu)) {
|
||||||
|
obj[obj.zhuan_ye_zu as string] = `${obj.bu_han_shui_jin_e}`;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
97
src/pages/api/temp/outputfile.ts
Normal file
97
src/pages/api/temp/outputfile.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { createReadStream } from 'fs';
|
||||||
|
|
||||||
|
// 定义响应数据类型
|
||||||
|
interface ResponseData {
|
||||||
|
message: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excel文件存放目录(相对于项目根目录)
|
||||||
|
const EXCEL_DIR = path.join(process.cwd(), 'public', 'excel');
|
||||||
|
|
||||||
|
// 确保Excel目录存在
|
||||||
|
if (!fs.existsSync(EXCEL_DIR)) {
|
||||||
|
fs.mkdirSync(EXCEL_DIR, { recursive: true });
|
||||||
|
console.log(`创建Excel目录: ${EXCEL_DIR}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 此处代码没有测试以及优化
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 仅允许GET请求
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(405).json({ message: '不支持的请求方法' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取查询参数中的文件名
|
||||||
|
const { filename } = req.query;
|
||||||
|
if (!filename || typeof filename !== 'string') {
|
||||||
|
return res.status(400).json({ message: '缺少或无效的文件名参数' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证文件名,防止路径遍历攻击
|
||||||
|
if (filename.includes('..') || path.isAbsolute(filename)) {
|
||||||
|
return res.status(400).json({ message: '文件名不允许包含路径遍历字符' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整文件路径
|
||||||
|
const filePath = path.join(EXCEL_DIR, filename);
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return res.status(404).json({ message: '文件不存在' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
const fileStats = fs.statSync(filePath);
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
res.setHeader('Content-Type', getContentType(filename));
|
||||||
|
res.setHeader('Content-Length', fileStats.size.toString());
|
||||||
|
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||||
|
|
||||||
|
// 创建文件读取流并管道到响应
|
||||||
|
const fileStream = createReadStream(filePath);
|
||||||
|
fileStream.pipe(res);
|
||||||
|
|
||||||
|
// 处理流错误
|
||||||
|
fileStream.on('error', (error) => {
|
||||||
|
console.error('文件流错误:', error);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({ message: '文件读取错误', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 流结束时结束响应
|
||||||
|
fileStream.on('end', () => {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理文件错误:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
message: '处理文件时发生错误',
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据文件名获取Content-Type
|
||||||
|
function getContentType(filename: string): string {
|
||||||
|
const ext = path.extname(filename).toLowerCase();
|
||||||
|
switch (ext) {
|
||||||
|
case '.xlsx':
|
||||||
|
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||||
|
case '.xls':
|
||||||
|
return 'application/vnd.ms-excel';
|
||||||
|
case '.csv':
|
||||||
|
return 'text/csv';
|
||||||
|
default:
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/pages/api/temp/testdb.ts
Normal file
46
src/pages/api/temp/testdb.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
// import { PrismaClient } from '@prisma/client'
|
||||||
|
import prismaClient from '@/lib/prisma'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import BigNumber from "bignumber.js";
|
||||||
|
type ResponseData = {
|
||||||
|
message: string
|
||||||
|
env?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>
|
||||||
|
) {
|
||||||
|
|
||||||
|
// 查询所有用户
|
||||||
|
// const users = await prismaClient.test.findMany()
|
||||||
|
try {
|
||||||
|
let x = new BigNumber(123.4567);
|
||||||
|
let a = new BigNumber('0.1');
|
||||||
|
let b = new BigNumber('0.2');
|
||||||
|
let y = new BigNumber(undefined);
|
||||||
|
let z = new BigNumber(x);
|
||||||
|
let o = a.plus(b);
|
||||||
|
const tax2 = new BigNumber(2.995);
|
||||||
|
console.log(tax2.toFixed(2)); // 输出:"3.00"(正确)
|
||||||
|
|
||||||
|
console.log()
|
||||||
|
|
||||||
|
// await prismaClient.test.create({
|
||||||
|
// data: {
|
||||||
|
// id: 2,
|
||||||
|
// name: 'Alice',
|
||||||
|
// shcool: 'alice@prisma.io',
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const timestamp = dayjs().subtract(1, 'day').valueOf();
|
||||||
|
console.log(timestamp);
|
||||||
|
res.status(200).json({ message: 'Hello from Next.js!', env: process.env.ENVIRONMENT })
|
||||||
|
}
|
||||||
410
src/pages/api/type.ts
Normal file
410
src/pages/api/type.ts
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
export interface InvoiceApplicationItem {
|
||||||
|
'序号': string;
|
||||||
|
'数据id': string;
|
||||||
|
'申请日期': string;
|
||||||
|
'申请人': string;
|
||||||
|
'申请部门': string;
|
||||||
|
'开票类型': string;
|
||||||
|
'项目组别': string; //
|
||||||
|
'预计报销第三方成本(元)': string;
|
||||||
|
'业务开始时间': string;
|
||||||
|
'业务结束时间': string;
|
||||||
|
'合同编号': string;
|
||||||
|
'合同名称': string;
|
||||||
|
'上传合同附件': string;
|
||||||
|
'项目编号': string;
|
||||||
|
'是否含税': string;
|
||||||
|
'开票税率': string;
|
||||||
|
'是否多项目合并': string;
|
||||||
|
'多项目合并开票请添加': string;
|
||||||
|
'多项目合并开票请添加_1': string;
|
||||||
|
'多项目合并开票请添加_2': string;
|
||||||
|
'多项目合并开票请添加_3': string;
|
||||||
|
'上传盖章费用清单': string;
|
||||||
|
'客户单位名称': string;
|
||||||
|
'统一社会信用代码': string;
|
||||||
|
'开户行名称': string;
|
||||||
|
'银行账号': string;
|
||||||
|
'发票税目': string;
|
||||||
|
'业务内容(备注)': string;
|
||||||
|
'开票金额明细': string;
|
||||||
|
'开票金额明细_1': string;
|
||||||
|
'开票金额明细_2': string;
|
||||||
|
'开票金额明细_3': string;
|
||||||
|
'开票金额(元)独': string;
|
||||||
|
'不含税金额(元)独': string;
|
||||||
|
'不含税金额(元)独(大写)': string;
|
||||||
|
'含税金额(元)独': string;
|
||||||
|
'含税金额(元)独(大写)': string;
|
||||||
|
'不含税金额(元)多': string;
|
||||||
|
'不含税金额(元)多(大写)': string;
|
||||||
|
'含税金额(元)多': string;
|
||||||
|
'含税金额(元)多(大写)': string;
|
||||||
|
'不含税金额(元)大': string;
|
||||||
|
'含税总金额(元)大': string;
|
||||||
|
'不含税金额(元)(大+独)': string;
|
||||||
|
'不含税金额(元)(大+独)(大写)': string;
|
||||||
|
'含税总金额(元)(大+独)': string;
|
||||||
|
'含税总金额(元)(大+独)(大写)': string;
|
||||||
|
'审批编号': string;
|
||||||
|
'创建时间': string;
|
||||||
|
'创建人': string;
|
||||||
|
'当前负责人': string;
|
||||||
|
'审批结果': string;
|
||||||
|
'审批状态': string;
|
||||||
|
'更新时间': string;
|
||||||
|
'创建人部门': string;
|
||||||
|
'审批单标题': string;
|
||||||
|
'历史审批人': string;
|
||||||
|
'耗时(时:分:秒)': string;
|
||||||
|
'审批记录': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoiceApplicationDBItem {
|
||||||
|
dataId: string;
|
||||||
|
applicationDate: string;
|
||||||
|
applicant: string;
|
||||||
|
applicationDepartment: string;
|
||||||
|
agreementType: string;
|
||||||
|
xiang_mu_zu_bie: string; // 项目组别
|
||||||
|
yuJiBaoXiaoDiSanFangChengBenYuan: string;
|
||||||
|
businessStartTime: string;
|
||||||
|
businessEndTime: string;
|
||||||
|
contractNumber: string;
|
||||||
|
contractName: string;
|
||||||
|
uploadedContractAttachment: string;
|
||||||
|
projectNumber: string;
|
||||||
|
is_tax_inclusive: string;
|
||||||
|
invoice_tax_rate: string;
|
||||||
|
is_multi_project_merge: string; // 是否多项目合并
|
||||||
|
uploadedSealedCostList: string;
|
||||||
|
customerCompanyName: string;
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma: string;
|
||||||
|
bankName: string;
|
||||||
|
bankAccount: string;
|
||||||
|
invoice_tax_category: string;
|
||||||
|
organizationType: string; // 单位性质
|
||||||
|
business_content_du: string; // 业务内容(独)
|
||||||
|
invoice_amount_yuan_du: string;
|
||||||
|
bu_han_shui_jin_e_yuan_du: string;
|
||||||
|
// bu_han_shui_jin_e_yuan_du_da_xie: string;
|
||||||
|
han_shui_jin_e_yuan_du: string;
|
||||||
|
// han_shui_jin_e_yuan_du_da_xie: string;
|
||||||
|
bu_han_shui_jin_e_yuan_duo: string; // 不含税金额(元)多
|
||||||
|
// bu_han_shui_jin_e_yuan_duo_da_xie: string; // 不含税金额(元)多(大写)
|
||||||
|
han_shui_jin_e_yuan_duo: string; // 含税金额(元)多
|
||||||
|
// han_shui_jin_e_yuan_duo_da_xie: string; // 含税金额(元)多(大写)
|
||||||
|
bu_han_shui_jin_e_yuan_da: string;
|
||||||
|
han_shui_zong_jin_e_yuan_da: string;
|
||||||
|
bu_han_shui_jin_e_yuan_da_du: string;
|
||||||
|
// bu_han_shui_jin_e_yuan_da_du_da_xie: string;
|
||||||
|
han_shui_zong_jin_e_yuan_da_du: string;
|
||||||
|
// han_shui_zong_jin_e_yuan_da_du_da_xie: string;
|
||||||
|
approvalNumber: string;
|
||||||
|
creationTime: string;
|
||||||
|
creator: string;
|
||||||
|
currentResponsiblePerson: string;
|
||||||
|
approvalResult: string;
|
||||||
|
approvalStatus: string;
|
||||||
|
updateTime: string;
|
||||||
|
creatorDepartment: string;
|
||||||
|
approvalFormTitle: string;
|
||||||
|
historicalApprovers: string;
|
||||||
|
timeConsumed: string;
|
||||||
|
approvalRecord: string;
|
||||||
|
BigInvoiceAmountList: InvoiceApplicationAmountDetails[];
|
||||||
|
multiProjectMergeInvoice: InvoiceApplicationAmountDetails[];
|
||||||
|
|
||||||
|
// 新增字段
|
||||||
|
kai_piao_shi_you?: string;
|
||||||
|
fa_piao_bei_zhu_lan_tian_xie_nei_rong?: string;
|
||||||
|
invoiceType?: string; // 中文原值
|
||||||
|
invoiceCode?: number; // 1=专用 2=普通
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
export interface InvoiceApplicationAmountDetails {
|
||||||
|
id?: number;
|
||||||
|
dataid: string;
|
||||||
|
agreementType: string;
|
||||||
|
project_number: string;
|
||||||
|
business_group_name: string;
|
||||||
|
project_name: string;
|
||||||
|
business_content: string;
|
||||||
|
invoice_amount: string;
|
||||||
|
yu_ji_bao_xiao_di_san_fang_cheng_ben?: string;
|
||||||
|
|
||||||
|
// 新增:单合同多家单位开票清单的扩展字段
|
||||||
|
customer_company_name?: string;
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma?: string;
|
||||||
|
bank_name?: string;
|
||||||
|
bank_account?: string;
|
||||||
|
payee?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InvoiceDataDBItem {
|
||||||
|
dataid: string;
|
||||||
|
piao_hao: string;
|
||||||
|
kai_piao_ri_qi: string;
|
||||||
|
hui_kuan_ri_qi: string;
|
||||||
|
hui_kuan_jin_e: string;
|
||||||
|
zhang_qi: string;
|
||||||
|
invoice_type_enum: number;
|
||||||
|
customer_company_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义发票数据项的类型
|
||||||
|
export type InvoiceDataItem = {
|
||||||
|
applicationDepartment?: string | null;
|
||||||
|
invoiceJobNumber: string | null;
|
||||||
|
dataId: string;
|
||||||
|
approvalNumber?: string | null;
|
||||||
|
unit_nature?: string | null;
|
||||||
|
agreementType?: string | null;
|
||||||
|
is_multi_project_merge?: string | null;
|
||||||
|
projectNumber?: string | null;
|
||||||
|
customerCompanyName?: string | null;
|
||||||
|
piao_hao?: string | null;
|
||||||
|
bu_han_shui_jin_e_yuan_du?: string | null;
|
||||||
|
han_shui_jin_e_yuan_du?: string | null;
|
||||||
|
bu_han_shui_jin_e_yuan_da?: string | null;
|
||||||
|
han_shui_jin_e_yuan_da?: string | null;
|
||||||
|
bu_han_shui_jin_e_yuan_da_du?: string | null;
|
||||||
|
han_shui_jin_e_yuan_da_du?: string | null;
|
||||||
|
bu_han_shui_jin_e_yuan_duo?: string | null;
|
||||||
|
han_shui_jin_e_yuan_duo?: string | null;
|
||||||
|
|
||||||
|
applicant?: string | null;
|
||||||
|
zhuan_ye_zu?: string | null;
|
||||||
|
kai_piao_ri_qi?: string | null;
|
||||||
|
hui_kuan_ri_qi?: string | null;
|
||||||
|
hui_kuan_jin_e?: string | null;
|
||||||
|
zhang_qi?: string | null;
|
||||||
|
business_content_du?: string | null;
|
||||||
|
|
||||||
|
invoice_type_enum?: number | null;
|
||||||
|
// 新增字段
|
||||||
|
kai_piao_shi_you?: string | null;
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma?: string | null;
|
||||||
|
bankName?: string | null;
|
||||||
|
bankAccount?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义处理后的发票数据项类型
|
||||||
|
export type ProcessedInvoiceDataItem = InvoiceDataItem & {
|
||||||
|
customerCompanyName: string;
|
||||||
|
agreementTypeName: string;
|
||||||
|
bu_han_shui_jin_e: number;
|
||||||
|
shui_e: number;
|
||||||
|
han_shui_zong_jin_e: number;
|
||||||
|
business_content: string;
|
||||||
|
xiang_mu_zu_bie: string;
|
||||||
|
is_multi_project_merge: string;
|
||||||
|
organizationType: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ProcessedInvoiceDataItemFromFunction {
|
||||||
|
dataId: string;
|
||||||
|
projectNumber: string;
|
||||||
|
agreementType: string | null;
|
||||||
|
agreementTypeName: string;
|
||||||
|
invoiceType?: string | null;
|
||||||
|
customerCompanyName: string;
|
||||||
|
piao_hao: string | null;
|
||||||
|
bu_han_shui_jin_e: number;
|
||||||
|
shui_e: number;
|
||||||
|
han_shui_zong_jin_e: number;
|
||||||
|
applicant: string | null;
|
||||||
|
zhuan_ye_zu: string | null;
|
||||||
|
kai_piao_ri_qi: string | null;
|
||||||
|
hui_kuan_ri_qi: string | null;
|
||||||
|
hui_kuan_jin_e: string | null;
|
||||||
|
business_content: string;
|
||||||
|
zhang_qi: string | null;
|
||||||
|
// 新增:透传给前端使用
|
||||||
|
tong_yi_she_hui_xin_yong_dai_ma?: string | null;
|
||||||
|
bankName?: string | null;
|
||||||
|
bankAccount?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用导入接口配置类型
|
||||||
|
export interface ImportConfig {
|
||||||
|
tableName: string; // Prisma模型名称
|
||||||
|
fieldMapping: { [excelKey: string]: string }; // Excel列名到数据库字段的映射
|
||||||
|
idField: string; // ID字段名
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用导入接口支持的表配置
|
||||||
|
export interface TableConfigs {
|
||||||
|
[key: string]: ImportConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseContractOrderManagementItem {
|
||||||
|
'数据id': string;
|
||||||
|
'采购合同编号': string;
|
||||||
|
'采购用途': string;
|
||||||
|
'项目编号(测试使用,非必填项)': string;
|
||||||
|
'关联销售合同编号(测试使用,非必填项)': string;
|
||||||
|
'合同名称(测试使用,非必填项)': string;
|
||||||
|
'采购审批单号(请先填写合同编号)': string;
|
||||||
|
'采购合同名称(***请再次确认合同名称***)': string;
|
||||||
|
'相对方': string;
|
||||||
|
'合同类别(是否开口)': string;
|
||||||
|
'合同总金额(含税)': string;
|
||||||
|
'剩余金额(含税)': string;
|
||||||
|
'合同剩余金额提醒': string;
|
||||||
|
'本次订单名称': string;
|
||||||
|
'本次订单金额(含税)': string;
|
||||||
|
'本次订单履约开始时间': string;
|
||||||
|
'本次订单履约结束时间': string;
|
||||||
|
'本次订单发起部门': string;
|
||||||
|
'本次订单发起人': string;
|
||||||
|
'本次订单发起时间': string;
|
||||||
|
'审批编号': string;
|
||||||
|
'创建时间': string;
|
||||||
|
'创建人': string;
|
||||||
|
'当前负责人': string;
|
||||||
|
'审批结果': string;
|
||||||
|
'审批状态': string;
|
||||||
|
'更新时间': string;
|
||||||
|
'创建人部门': string;
|
||||||
|
'审批单标题': string;
|
||||||
|
'耗时(时:分:秒)': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseContractOrderManagementDBItem {
|
||||||
|
data_id: string;
|
||||||
|
purchase_contract_number: string;
|
||||||
|
purchase_purpose: string;
|
||||||
|
project_number: string;
|
||||||
|
associated_sales_contract_number: string;
|
||||||
|
contract_name: string;
|
||||||
|
purchase_approval_number: string;
|
||||||
|
purchase_contract_name: string;
|
||||||
|
xiang_dui_fang: string;
|
||||||
|
he_tong_lei_bie: string;
|
||||||
|
he_tong_zong_jin_e_HanShui: string;
|
||||||
|
sheng_yu_jin_e_HanShui: string;
|
||||||
|
he_tong_sheng_yu_jin_e_ti_xing: string;
|
||||||
|
ben_ci_ding_dan_ming_cheng: string;
|
||||||
|
ben_ci_ding_dan_jin_e_HanShui: string;
|
||||||
|
ben_ci_ding_dan_lu_yue_kai_shi_shi_jian: string;
|
||||||
|
ben_ci_ding_dan_lu_yue_jie_shu_shi_jian: string;
|
||||||
|
ben_ci_ding_dan_fa_qi_bu_men: string;
|
||||||
|
ben_ci_ding_dan_fa_qi_ren: string;
|
||||||
|
ben_ci_ding_dan_fa_qi_shi_jian: string;
|
||||||
|
approval_number: string;
|
||||||
|
creation_time: string;
|
||||||
|
creator: string;
|
||||||
|
current_responsible_person: string;
|
||||||
|
approval_result: string;
|
||||||
|
approval_status: string;
|
||||||
|
update_time: string;
|
||||||
|
creator_department: string;
|
||||||
|
approval_form_title: string;
|
||||||
|
time_consumed: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface PurchaseContractExecutionMaintenanceItem {
|
||||||
|
'数据id': string;
|
||||||
|
'发起人': string;
|
||||||
|
'项目编号': string;
|
||||||
|
'合同编号': string;
|
||||||
|
'合同名称': string;
|
||||||
|
'履约发起部门': string;
|
||||||
|
'经办人': string;
|
||||||
|
'相对方': string;
|
||||||
|
'是否开口': string;
|
||||||
|
'合同总额': string;
|
||||||
|
'备注': string;
|
||||||
|
'合同余额': string;
|
||||||
|
'履约开始时间': string;
|
||||||
|
'合同原件扫描件(盖章版)': string;
|
||||||
|
'钉钉采购审批单': string;
|
||||||
|
'合同附件扫描件': string;
|
||||||
|
'数据库索引': string;
|
||||||
|
'审批编号': string;
|
||||||
|
'创建时间': string;
|
||||||
|
'创建人': string;
|
||||||
|
'当前负责人': string;
|
||||||
|
'审批结果': string;
|
||||||
|
'审批状态': string;
|
||||||
|
'更新时间': string;
|
||||||
|
'创建人部门': string;
|
||||||
|
'审批单标题': string;
|
||||||
|
'历史审批人': string;
|
||||||
|
'耗时(时:分:秒)': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PurchaseContractExecutionMaintenanceDBItem {
|
||||||
|
data_id: string;
|
||||||
|
initiator: string;
|
||||||
|
project_number: string;
|
||||||
|
contract_number: string;
|
||||||
|
contract_name: string;
|
||||||
|
lvyue_faqi_bumen: string;
|
||||||
|
operator: string;
|
||||||
|
xiangduifang: string;
|
||||||
|
is_open: string;
|
||||||
|
contract_amount: string;
|
||||||
|
remarks: string;
|
||||||
|
contract_balance: string;
|
||||||
|
performance_start_time: string;
|
||||||
|
hetong_yuanjian_saomiaojian_gaizhangban: string;
|
||||||
|
dingtalk_procurement_approval: string;
|
||||||
|
contract_attachment_scan: string;
|
||||||
|
database_index: string;
|
||||||
|
approval_number: string;
|
||||||
|
creation_time: string;
|
||||||
|
creator: string;
|
||||||
|
current_responsible: string;
|
||||||
|
approval_result: string;
|
||||||
|
approval_status: string;
|
||||||
|
update_time: string;
|
||||||
|
creator_department: string;
|
||||||
|
approval_form_title: string;
|
||||||
|
historical_approvers: string;
|
||||||
|
time_consumed: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ProjectItem {
|
||||||
|
'数据id': string;
|
||||||
|
'组别缩写': string;
|
||||||
|
'上一项目编号': string;
|
||||||
|
'项目编号': string;
|
||||||
|
'项目名称': string;
|
||||||
|
'关联合同编号': string;
|
||||||
|
'合同名称': string;
|
||||||
|
'客户名称': string;
|
||||||
|
'交付区域(省/市)': string;
|
||||||
|
'单位性质': string;
|
||||||
|
'项目主担': string;
|
||||||
|
'项目组别': string;
|
||||||
|
'创建时间': string;
|
||||||
|
'创建者': string;
|
||||||
|
'创建人部门': string;
|
||||||
|
'审批单标题': string;
|
||||||
|
'更新时间': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectDBItem {
|
||||||
|
data_id: string;
|
||||||
|
group_abbreviation: string;
|
||||||
|
previous_project_id: string;
|
||||||
|
project_id: string;
|
||||||
|
project_name: string;
|
||||||
|
contract_id: string;
|
||||||
|
contract_name: string;
|
||||||
|
client_name: string;
|
||||||
|
delivery_area: string;
|
||||||
|
unit_nature: string;
|
||||||
|
project_leader: string;
|
||||||
|
project_group: string;
|
||||||
|
create_time: string;
|
||||||
|
creator: string;
|
||||||
|
creator_department: string;
|
||||||
|
approval_title: string;
|
||||||
|
update_time: string;
|
||||||
|
}
|
||||||
315
src/pages/mockjson/getInvoiceApprovalData.json
Normal file
315
src/pages/mockjson/getInvoiceApprovalData.json
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
{
|
||||||
|
"message": "Hello from Next.js! ",
|
||||||
|
"testjsonres": {
|
||||||
|
"headers": {
|
||||||
|
"server": "DingTalk/1.0.0",
|
||||||
|
"date": "Thu, 17 Jul 2025 09:37:20 GMT",
|
||||||
|
"content-type": "application/json;charset=utf-8",
|
||||||
|
"content-length": "8541",
|
||||||
|
"connection": "keep-alive",
|
||||||
|
"vary": "Accept-Encoding, Accept-Encoding",
|
||||||
|
"access-control-allow-origin": "*",
|
||||||
|
"access-control-expose-headers": "*",
|
||||||
|
"x-acs-request-id": "AA91573E-F016-7F65-B8A5-F27505F8EA18",
|
||||||
|
"x-acs-trace-id": "8c641caa4da3bb4c9feb718781fdacec",
|
||||||
|
"etag": "7vNso+AtO0PfoeVdtZAIK0w1",
|
||||||
|
"access-control-allow-headers": "X-Requested-With, X-Sequence, _aop_secret, _aop_signature, x-acs-dingtalk-access-token"
|
||||||
|
},
|
||||||
|
"statusCode": 200,
|
||||||
|
"body": {
|
||||||
|
"result": {
|
||||||
|
"attachedProcessInstanceIds": [],
|
||||||
|
"bizAction": "NONE",
|
||||||
|
"bizData": "{}",
|
||||||
|
"businessId": "202507090915000418108",
|
||||||
|
"ccUserIds": [
|
||||||
|
"216969073232379411"
|
||||||
|
],
|
||||||
|
"createTime": "2025-07-09T09:15Z",
|
||||||
|
"finishTime": "2025-07-09T09:16Z",
|
||||||
|
"formComponentValues": [
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDDateField",
|
||||||
|
"id": "DDDateField_13V7MHUHVIIO0",
|
||||||
|
"name": "申请日期",
|
||||||
|
"value": "2025-07-09"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "InnerContactField",
|
||||||
|
"extValue": "[{\"emplId\":\"216969073232379411\",\"itemId\":\"216969073232379411\",\"name\":\"胡光涛\",\"avatar\":\"https://static.dingtalk.com/media/lQLPM4IlQBbhggPNAyDNAyCwvNY1MzXKvZwFdbEHbuBPAA_800_800.png\"}]",
|
||||||
|
"id": "InnerContactField_OWGVHGJ10XS0",
|
||||||
|
"name": "申请人",
|
||||||
|
"value": "胡光涛"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DepartmentField",
|
||||||
|
"extValue": "[{\"itemId\":665065400,\"name\":\"运营室\",\"id\":665065400,\"fullDeptName\":\"运营室-运营室\"}]",
|
||||||
|
"id": "DepartmentField_1QNRM3JZDCLC0",
|
||||||
|
"name": "申请部门",
|
||||||
|
"value": "运营室"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDSelectField",
|
||||||
|
"extValue": "{\"label\":\"大协议\",\"key\":\"option_0\"}",
|
||||||
|
"id": "DDSelectField_MA0M7TBUYWG0",
|
||||||
|
"name": "开票类型",
|
||||||
|
"value": "大协议"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_1E8XNBRX7TC00",
|
||||||
|
"name": "预计报销第三方成本(元)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDDateRangeField",
|
||||||
|
"id": "DDDateRangeField_1T26OKK5W2XS0",
|
||||||
|
"name": "[\"业务开始时间\",\"业务结束时间\"]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDSelectField",
|
||||||
|
"id": "DDSelectField_R8TB2L9FFDC0",
|
||||||
|
"name": "项目编号"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_1KFIQW86NZFK0",
|
||||||
|
"name": "合同编号"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_1AEB73361Y5C0",
|
||||||
|
"name": "合同名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "FormRelateField",
|
||||||
|
"id": "FormRelateField_T0MH0PPK4PS0",
|
||||||
|
"name": "合同管理"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDAttachment",
|
||||||
|
"id": "DDAttachment_1MEL4XXTWXVK0",
|
||||||
|
"name": "上传盖章费用清单"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDSelectField",
|
||||||
|
"extValue": "{\"label\":\"凌悦数字科技(武汉)有限公司\",\"key\":\"twR6_C5bQTGFUq29EK18vQ08211744200895\"}",
|
||||||
|
"id": "DDSelectField_VYKH4VFBEQO0",
|
||||||
|
"name": "客户单位名称(大协议)",
|
||||||
|
"value": "凌悦数字科技(武汉)有限公司"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_103IX9MP82PC0",
|
||||||
|
"name": "统一社会信用代码(税号)",
|
||||||
|
"value": "91420100MA7MX5417J"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_64DY8T2JAK80",
|
||||||
|
"name": "开户行名称",
|
||||||
|
"value": "招商银行武汉分行经济技术开发区支行"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_1YDT2JKYGCIO0",
|
||||||
|
"name": "银行账号",
|
||||||
|
"value": "127916557910601"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "AddressField",
|
||||||
|
"extValue": "{\"province\":{\"name\":\"湖北省\",\"id\":\"420000\"},\"city\":{\"name\":\"武汉市\",\"id\":\"420100\"},\"district\":{\"name\":\"武汉市\",\"id\":\"420100\"}}",
|
||||||
|
"id": "AddressField_UH33ZMYZ6EO0",
|
||||||
|
"name": "负责地区",
|
||||||
|
"value": "湖北省,武汉市"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDSelectField",
|
||||||
|
"extValue": "{\"label\":\"*人力资源服务*其他人力资源服务\",\"key\":\"option_0\"}",
|
||||||
|
"id": "DDSelectField_ZXX2MGPMIXC0",
|
||||||
|
"name": "发票税目",
|
||||||
|
"value": "*人力资源服务*其他人力资源服务"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TableField",
|
||||||
|
"extValue": "{\"statValue\":[],\"componentName\":\"TableField\"}",
|
||||||
|
"id": "TableField_QEXZCW3ZPXS0",
|
||||||
|
"name": "开票金额明细(大协议)",
|
||||||
|
"value": "[{\"rowValue\":[{\"label\":\"业务组名称\",\"extendValue\":[{\"itemId\":665065400,\"name\":\"运营室\",\"id\":665065400,\"fullDeptName\":\"运营室-运营室\"}],\"source\":\"system\",\"value\":\"运营室\",\"key\":\"DepartmentField_1BBHYIYCFQJK0\"},{\"label\":\"项目名称\",\"value\":\"测试(项目名称)\",\"key\":\"TextField_12GMJ6Y62POW0\"},{\"label\":\"是否含税\",\"extendValue\":{\"label\":\"是 \",\"key\":\"option_0\"},\"value\":\"是 \",\"key\":\"DDSelectField_2Z2HLVP44SC0\"},{\"label\":\"开票税率\",\"extendValue\":{\"label\":\"0.06\",\"key\":\"option_0\"},\"value\":\"0.06\",\"key\":\"DDSelectField_1YFZ1Y6YSDOG0\"},{\"label\":\"不含税金额(元)\",\"value\":\"15000\",\"key\":\"MoneyField_7U5GR5MZ0UO0\"},{\"label\":\"税额(元)\",\"source\":\"system\",\"value\":\"900.00\",\"key\":\"MoneyField_YK6EMUSBTK00\"},{\"label\":\"总金额(元)\",\"source\":\"system\",\"value\":\"15900.00\",\"key\":\"MoneyField_1HPIZ8GLETOG0\"}],\"rowNumber\":\"TableField_QEXZCW3ZPXS0_21VBDXI9TFNK0\"},{\"rowValue\":[{\"label\":\"业务组名称\",\"extendValue\":[{\"deptName\":\"客户服务室\",\"itemId\":\"982357292\",\"name\":\"客户服务室\",\"id\":\"982357292\"}],\"value\":\"客户服务室\",\"key\":\"DepartmentField_1BBHYIYCFQJK0\"},{\"label\":\"项目名称\",\"value\":\"测试(项目名称客户)\",\"key\":\"TextField_12GMJ6Y62POW0\"},{\"label\":\"是否含税\",\"extendValue\":{\"label\":\"否\",\"key\":\"option_1\"},\"value\":\"否\",\"key\":\"DDSelectField_2Z2HLVP44SC0\"},{\"label\":\"不含税金额(元)\",\"value\":\"25000\",\"key\":\"MoneyField_7U5GR5MZ0UO0\"},{\"label\":\"总金额(元)\",\"source\":\"system\",\"value\":\"25000.00\",\"key\":\"MoneyField_1HPIZ8GLETOG0\"}],\"rowNumber\":\"TableField_QEXZCW3ZPXS0_167AI8YI64HS0\"}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_1UYLNSUA86TC0",
|
||||||
|
"name": "客户单位名称(独立协议)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_1Z97XGZ345HC0",
|
||||||
|
"name": "统一社会领用代码(税号)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_22RX2SVA40TC0",
|
||||||
|
"name": "开户银行名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_21S87YGUK0BK0",
|
||||||
|
"name": "银行账号"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "TextField",
|
||||||
|
"id": "TextField_UYO8FRG98280",
|
||||||
|
"name": "负责地区"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "DDSelectField",
|
||||||
|
"id": "DDSelectField_J6LHLQNUQ1S0",
|
||||||
|
"name": "发票税目"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_SUUWS7GXHVK0",
|
||||||
|
"name": "不含税金额(元)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_1RPVEX0YUYLC0",
|
||||||
|
"name": "总金额(元)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_1CA9FPSAZURK0",
|
||||||
|
"name": "不含税金额(元)大",
|
||||||
|
"value": "40000.00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_IQ8O9U5HXRS0",
|
||||||
|
"name": "总金额(元)大",
|
||||||
|
"value": "40900.00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_AJ2560YHU4K0",
|
||||||
|
"name": "不含税金额(元)(大+独)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "MoneyField",
|
||||||
|
"id": "MoneyField_183UEPMEZ4IO0",
|
||||||
|
"name": "含税总金额(元)(大+独)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bizAlias": "",
|
||||||
|
"componentType": "SeqNumberField",
|
||||||
|
"id": "SeqNumberField_1U5LXXTMOS8W0",
|
||||||
|
"name": "开票流水号",
|
||||||
|
"value": "202500001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationRecords": [
|
||||||
|
{
|
||||||
|
"date": "2025-07-09T09:15Z",
|
||||||
|
"images": [],
|
||||||
|
"result": "NONE",
|
||||||
|
"showName": "提交申请",
|
||||||
|
"type": "START_PROCESS_INSTANCE",
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"activityId": "2927_72a8",
|
||||||
|
"date": "2025-07-09T09:15Z",
|
||||||
|
"images": [],
|
||||||
|
"result": "AGREE",
|
||||||
|
"showName": "直接主管",
|
||||||
|
"type": "EXECUTE_TASK_NORMAL",
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"activityId": "f3f7_8c89",
|
||||||
|
"date": "2025-07-09T09:16Z",
|
||||||
|
"images": [],
|
||||||
|
"remark": "",
|
||||||
|
"result": "AGREE",
|
||||||
|
"showName": "开票人员",
|
||||||
|
"type": "EXECUTE_TASK_NORMAL",
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"activityId": "9955_7e43",
|
||||||
|
"ccUserIds": [
|
||||||
|
"216969073232379411"
|
||||||
|
],
|
||||||
|
"date": "2025-07-09T09:16Z",
|
||||||
|
"images": [],
|
||||||
|
"remark": "",
|
||||||
|
"result": "NONE",
|
||||||
|
"showName": "抄送人",
|
||||||
|
"type": "PROCESS_CC",
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"originatorDeptId": "665065400",
|
||||||
|
"originatorDeptName": "运营室-运营室",
|
||||||
|
"originatorUserId": "216969073232379411",
|
||||||
|
"result": "agree",
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"activityId": "2927_72a8",
|
||||||
|
"createTime": "2025-07-09T09:15Z",
|
||||||
|
"finishTime": "2025-07-09T09:15Z",
|
||||||
|
"mobileUrl": "aflow.dingtalk.com?procInsId=Gx01t4YsSMiKyotJTTdcPw08211752023741&taskId=94820045176&businessId=202507090915000418108",
|
||||||
|
"pcUrl": "aflow.dingtalk.com?procInsId=Gx01t4YsSMiKyotJTTdcPw08211752023741&taskId=94820045176&businessId=202507090915000418108",
|
||||||
|
"result": "AGREE",
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"taskId": 94820045176,
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"activityId": "f3f7_8c89",
|
||||||
|
"createTime": "2025-07-09T09:15Z",
|
||||||
|
"finishTime": "2025-07-09T09:16Z",
|
||||||
|
"mobileUrl": "aflow.dingtalk.com?procInsId=Gx01t4YsSMiKyotJTTdcPw08211752023741&taskId=94840972088&businessId=202507090915000418108",
|
||||||
|
"pcUrl": "aflow.dingtalk.com?procInsId=Gx01t4YsSMiKyotJTTdcPw08211752023741&taskId=94840972088&businessId=202507090915000418108",
|
||||||
|
"result": "AGREE",
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"taskId": 94840972088,
|
||||||
|
"userId": "216969073232379411"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "胡光涛提交的开票申请"
|
||||||
|
},
|
||||||
|
"success": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/types/dingtalk.d.ts
vendored
Normal file
4
src/types/dingtalk.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '@alicloud/dingtalk' {
|
||||||
|
const dingtalk: any;
|
||||||
|
export = dingtalk;
|
||||||
|
}
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/types/**/*.d.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
271
views/procurement_db/invoice_application_match_inner_view.sql
Normal file
271
views/procurement_db/invoice_application_match_inner_view.sql
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
SELECT
|
||||||
|
`d`.`dataId` AS `dataId`,
|
||||||
|
`d`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`d`.`agreementType` AS `agreementType`,
|
||||||
|
`d`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`d`.`salesContractNumber` AS `salesContractNumber`,
|
||||||
|
`d`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`d`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`d`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`d`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`d`.`projectNumber` AS `projectNumber`,
|
||||||
|
`d`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`d`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`i`.`piao_hao` AS `piao_hao`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`d`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`d`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`d`.`applicant` AS `applicant`,
|
||||||
|
`d`.`business_content_du` AS `business_content_du`,
|
||||||
|
`d`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`d`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`d`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`d`.`invoiceType` AS `invoiceType`,
|
||||||
|
`d`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`d`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`d`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`d`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`d`.`bankName` AS `bankName`,
|
||||||
|
`d`.`bankAccount` AS `bankAccount`,
|
||||||
|
`i`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`i`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`i`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`i`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`i`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`c`.`customer_type` AS `unit_nature`,
|
||||||
|
`d`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`da`.`dataId` AS `dataId`,
|
||||||
|
`da`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`da`.`agreementType` AS `agreementType`,
|
||||||
|
`da`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`da`.`contractNumber` AS `salesContractNumber`,
|
||||||
|
`da`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`da`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`da`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`da`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`da`.`projectNumber` AS `projectNumber`,
|
||||||
|
`da`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`da`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_zong_jin_e_yuan_da`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_zong_jin_e_yuan_da_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`applicant` AS `applicant`,
|
||||||
|
`da`.`business_content_du` AS `business_content_du`,
|
||||||
|
`da`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`da`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`da`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`da`.`invoiceType` AS `invoiceType`,
|
||||||
|
`da`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`da`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`da`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`da`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`da`.`bankName` AS `bankName`,
|
||||||
|
`da`.`bankAccount` AS `bankAccount`,
|
||||||
|
`da`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da2`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`da2`.`dataId` = `da`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`da2`.`customerCompanyName` = `da`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
`da2`.`invoiceJobNumber` > `da`.`invoiceJobNumber`
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
(
|
||||||
|
`da2`.`invoiceJobNumber` = `da`.`invoiceJobNumber`
|
||||||
|
)
|
||||||
|
AND (`da2`.`dataId` > `da`.`dataId`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + 1
|
||||||
|
) AS `rn_da`,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da3`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`da3`.`dataId` = `da`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`da3`.`customerCompanyName` = `da`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) AS `cnt_da`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da`
|
||||||
|
)
|
||||||
|
) `d`
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`ib`.`dataid` AS `dataid`,
|
||||||
|
`ib`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`ib`.`piao_hao` AS `piao_hao`,
|
||||||
|
`ib`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`ib`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`ib`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`ib`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`ib`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`invoice_data`.`invoice_type_enum`
|
||||||
|
) AS `invoice_type_enum`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`kai_piao_ri_qi`) AS `kai_piao_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_ri_qi`) AS `hui_kuan_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_jin_e`) AS `hui_kuan_jin_e`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`zhang_qi`) AS `zhang_qi`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib2`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`ib2`.`dataid` = `ib`.`dataid`)
|
||||||
|
AND (
|
||||||
|
`ib2`.`customer_company_name` = `ib`.`customer_company_name`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
coalesce(`ib2`.`kai_piao_ri_qi`, '9999-12-31') < coalesce(`ib`.`kai_piao_ri_qi`, '9999-12-31')
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
(
|
||||||
|
coalesce(`ib2`.`kai_piao_ri_qi`, '9999-12-31') = coalesce(`ib`.`kai_piao_ri_qi`, '9999-12-31')
|
||||||
|
)
|
||||||
|
AND (`ib2`.`piao_hao` < `ib`.`piao_hao`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + 1
|
||||||
|
) AS `rn_id`,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib3`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`ib3`.`dataid` = `ib`.`dataid`)
|
||||||
|
AND (
|
||||||
|
`ib3`.`customer_company_name` = `ib`.`customer_company_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) AS `cnt_id`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`invoice_data`.`invoice_type_enum`
|
||||||
|
) AS `invoice_type_enum`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`kai_piao_ri_qi`) AS `kai_piao_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_ri_qi`) AS `hui_kuan_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_jin_e`) AS `hui_kuan_jin_e`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`zhang_qi`) AS `zhang_qi`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib`
|
||||||
|
) `i` ON(
|
||||||
|
(
|
||||||
|
(`i`.`dataid` = `d`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`i`.`customer_company_name` = `d`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
`d`.`rn_da` = (((`i`.`rn_id` - 1) % `d`.`cnt_da`) + 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`c1`.`customer_unit_name` AS `customer_unit_name`,
|
||||||
|
max(`c1`.`customer_type`) AS `customer_type`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
`procurement_db`.`customer_management_info` `c1`
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`customer_management_info`.`customer_unit_name` AS `customer_unit_name`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`customer_management_info`.`updated_at`
|
||||||
|
) AS `updated_at`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`customer_management_info`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`customer_management_info`.`customer_unit_name`
|
||||||
|
) `latest` ON(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`c1`.`customer_unit_name` = `latest`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
AND (`c1`.`updated_at` = `latest`.`updated_at`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
`c1`.`customer_unit_name`
|
||||||
|
) `c` ON(
|
||||||
|
(
|
||||||
|
`d`.`customerCompanyName` = `c`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
271
views/procurement_db/invoice_application_match_view.sql
Normal file
271
views/procurement_db/invoice_application_match_view.sql
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
SELECT
|
||||||
|
`d`.`dataId` AS `dataId`,
|
||||||
|
`d`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`d`.`agreementType` AS `agreementType`,
|
||||||
|
`d`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`d`.`salesContractNumber` AS `salesContractNumber`,
|
||||||
|
`d`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`d`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`d`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`d`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`d`.`projectNumber` AS `projectNumber`,
|
||||||
|
`d`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`d`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`i`.`piao_hao` AS `piao_hao`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`d`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_jin_e_yuan_da`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`d`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_jin_e_yuan_da_du`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`d`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`d`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`d`.`applicant` AS `applicant`,
|
||||||
|
`d`.`business_content_du` AS `business_content_du`,
|
||||||
|
`d`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`d`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`d`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`d`.`invoiceType` AS `invoiceType`,
|
||||||
|
`d`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`d`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`d`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`d`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`d`.`bankName` AS `bankName`,
|
||||||
|
`d`.`bankAccount` AS `bankAccount`,
|
||||||
|
`i`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`i`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`i`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`i`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`i`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
`c`.`customer_type` AS `unit_nature`,
|
||||||
|
`d`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`da`.`dataId` AS `dataId`,
|
||||||
|
`da`.`invoiceJobNumber` AS `invoiceJobNumber`,
|
||||||
|
`da`.`agreementType` AS `agreementType`,
|
||||||
|
`da`.`approvalNumber` AS `approvalNumber`,
|
||||||
|
`da`.`contractNumber` AS `salesContractNumber`,
|
||||||
|
`da`.`applicationDepartment` AS `applicationDepartment`,
|
||||||
|
`da`.`yuJiBaoXiaoDiSanFangChengBenYuan` AS `yuJiBaoXiaoDiSanFangChengBenYuan`,
|
||||||
|
`da`.`is_multi_project_merge` AS `is_multi_project_merge`,
|
||||||
|
`da`.`is_single_contract_multi_customer` AS `is_single_contract_multi_customer`,
|
||||||
|
`da`.`projectNumber` AS `projectNumber`,
|
||||||
|
`da`.`customerCompanyName` AS `customerCompanyName`,
|
||||||
|
`da`.`customerCompanyNameOther` AS `customerCompanyNameOther`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_du` AS `bu_han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_du` AS `han_shui_jin_e_yuan_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da` AS `bu_han_shui_jin_e_yuan_da`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da` AS `han_shui_zong_jin_e_yuan_da`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_da_du` AS `bu_han_shui_jin_e_yuan_da_du`,
|
||||||
|
`da`.`han_shui_zong_jin_e_yuan_da_du` AS `han_shui_zong_jin_e_yuan_da_du`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_duo` AS `bu_han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_duo` AS `han_shui_jin_e_yuan_duo`,
|
||||||
|
`da`.`bu_han_shui_jin_e_yuan_other` AS `bu_han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`han_shui_jin_e_yuan_other` AS `han_shui_jin_e_yuan_other`,
|
||||||
|
`da`.`applicant` AS `applicant`,
|
||||||
|
`da`.`business_content_du` AS `business_content_du`,
|
||||||
|
`da`.`uploadedContractAttachment` AS `uploadedContractAttachment`,
|
||||||
|
`da`.`uploadedSealedCostListDa` AS `uploadedSealedCostListDa`,
|
||||||
|
`da`.`uploadedSealedCostListDu` AS `uploadedSealedCostListDu`,
|
||||||
|
`da`.`invoiceType` AS `invoiceType`,
|
||||||
|
`da`.`invoiceCode` AS `invoiceCode`,
|
||||||
|
`da`.`kai_piao_shi_you` AS `kai_piao_shi_you`,
|
||||||
|
`da`.`fa_piao_bei_zhu_lan_tian_xie_nei_rong` AS `fa_piao_bei_zhu_lan_tian_xie_nei_rong`,
|
||||||
|
`da`.`tong_yi_she_hui_xin_yong_dai_ma` AS `tong_yi_she_hui_xin_yong_dai_ma`,
|
||||||
|
`da`.`bankName` AS `bankName`,
|
||||||
|
`da`.`bankAccount` AS `bankAccount`,
|
||||||
|
`da`.`hong_chong_piao_hao` AS `hong_chong_piao_hao`,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da2`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`da2`.`dataId` = `da`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`da2`.`customerCompanyName` = `da`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
`da2`.`invoiceJobNumber` > `da`.`invoiceJobNumber`
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
(
|
||||||
|
`da2`.`invoiceJobNumber` = `da`.`invoiceJobNumber`
|
||||||
|
)
|
||||||
|
AND (`da2`.`dataId` > `da`.`dataId`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + 1
|
||||||
|
) AS `rn_da`,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da3`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`da3`.`dataId` = `da`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`da3`.`customerCompanyName` = `da`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) AS `cnt_da`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`dingding_oa_invoice_application` `da`
|
||||||
|
)
|
||||||
|
) `d`
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`ib`.`dataid` AS `dataid`,
|
||||||
|
`ib`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`ib`.`piao_hao` AS `piao_hao`,
|
||||||
|
`ib`.`invoice_type_enum` AS `invoice_type_enum`,
|
||||||
|
`ib`.`kai_piao_ri_qi` AS `kai_piao_ri_qi`,
|
||||||
|
`ib`.`hui_kuan_ri_qi` AS `hui_kuan_ri_qi`,
|
||||||
|
`ib`.`hui_kuan_jin_e` AS `hui_kuan_jin_e`,
|
||||||
|
`ib`.`zhang_qi` AS `zhang_qi`,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`invoice_data`.`invoice_type_enum`
|
||||||
|
) AS `invoice_type_enum`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`kai_piao_ri_qi`) AS `kai_piao_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_ri_qi`) AS `hui_kuan_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_jin_e`) AS `hui_kuan_jin_e`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`zhang_qi`) AS `zhang_qi`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib2`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`ib2`.`dataid` = `ib`.`dataid`)
|
||||||
|
AND (
|
||||||
|
`ib2`.`customer_company_name` = `ib`.`customer_company_name`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(
|
||||||
|
coalesce(`ib2`.`kai_piao_ri_qi`, '9999-12-31') < coalesce(`ib`.`kai_piao_ri_qi`, '9999-12-31')
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
(
|
||||||
|
coalesce(`ib2`.`kai_piao_ri_qi`, '9999-12-31') = coalesce(`ib`.`kai_piao_ri_qi`, '9999-12-31')
|
||||||
|
)
|
||||||
|
AND (`ib2`.`piao_hao` < `ib`.`piao_hao`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) + 1
|
||||||
|
) AS `rn_id`,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
count(1)
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib3`
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(`ib3`.`dataid` = `ib`.`dataid`)
|
||||||
|
AND (
|
||||||
|
`ib3`.`customer_company_name` = `ib`.`customer_company_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) AS `cnt_id`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`invoice_data`.`dataid` AS `dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name` AS `customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao` AS `piao_hao`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`invoice_data`.`invoice_type_enum`
|
||||||
|
) AS `invoice_type_enum`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`kai_piao_ri_qi`) AS `kai_piao_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_ri_qi`) AS `hui_kuan_ri_qi`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`hui_kuan_jin_e`) AS `hui_kuan_jin_e`,
|
||||||
|
max(`procurement_db`.`invoice_data`.`zhang_qi`) AS `zhang_qi`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`invoice_data`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`invoice_data`.`dataid`,
|
||||||
|
`procurement_db`.`invoice_data`.`customer_company_name`,
|
||||||
|
`procurement_db`.`invoice_data`.`piao_hao`
|
||||||
|
) `ib`
|
||||||
|
) `i` ON(
|
||||||
|
(
|
||||||
|
(`i`.`dataid` = `d`.`dataId`)
|
||||||
|
AND (
|
||||||
|
`i`.`customer_company_name` = `d`.`customerCompanyName`
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
`d`.`rn_da` = (((`i`.`rn_id` - 1) % `d`.`cnt_da`) + 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
`c1`.`customer_unit_name` AS `customer_unit_name`,
|
||||||
|
max(`c1`.`customer_type`) AS `customer_type`
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
`procurement_db`.`customer_management_info` `c1`
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
`procurement_db`.`customer_management_info`.`customer_unit_name` AS `customer_unit_name`,
|
||||||
|
max(
|
||||||
|
`procurement_db`.`customer_management_info`.`updated_at`
|
||||||
|
) AS `updated_at`
|
||||||
|
FROM
|
||||||
|
`procurement_db`.`customer_management_info`
|
||||||
|
GROUP BY
|
||||||
|
`procurement_db`.`customer_management_info`.`customer_unit_name`
|
||||||
|
) `latest` ON(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
`c1`.`customer_unit_name` = `latest`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
AND (`c1`.`updated_at` = `latest`.`updated_at`)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
`c1`.`customer_unit_name`
|
||||||
|
) `c` ON(
|
||||||
|
(
|
||||||
|
`d`.`customerCompanyName` = `c`.`customer_unit_name`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user