doc: sql in README #2

Merged
SOR2171 merged 5 commits from backend-dev into main 2026-07-25 14:41:42 +08:00
54 changed files with 951 additions and 147 deletions
+1
View File
@@ -5,6 +5,7 @@
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$/src/beckend" />
<option name="gradleJvm" value="25" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/src/beckend" />
+12
View File
@@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredNames">
<list>
<option value="id" />
</list>
</option>
</inspection_tool>
</profile>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/schema.sql" dialect="MariaDB" />
</component>
</project>
+54 -14
View File
@@ -29,10 +29,10 @@
## 预计完成的 API 列表
### 1. **物品管理 (Items)** - `/api/`
### 1. **物品管理 (Items)**
| 端点 | 方法 | 功能说明 |
|---------------------------------|---------------------|----------------|
|----------------------------------------------|---------------------|--------------------------|
| `items/` | GET/POST/PUT/DELETE | 物品 CRUD 操作 |
| `items/{id}/upload_images/` | POST | 上传物品图片 |
| `items/{id}/set_primary_image/` | POST | 设置主图片 |
@@ -51,10 +51,10 @@
---
### 2. **人员管理 (Personnel)** - `/api/`
### 2. **人员管理 (Personnel)**
| 端点 | 方法 | 功能说明 |
|---------------------------------|---------------------|------------------|
|---------------------------------|---------------------|----------------------------------|
| `personnel/` | GET/POST/PUT/DELETE | 人员信息 CRUD 操作 |
| `personnel/statistics/` | GET | 获取人员统计信息(按部门和职位) |
| `personnel/{id}/set_inactive/` | POST | 设置人员为已卸任状态 |
@@ -69,10 +69,10 @@
---
### 3. **财务管理 (Finance)** - `/api/`
### 3. **财务管理 (Finance)**
| 端点 | 方法 | 功能说明 |
|-------------------------------|---------------------|------------------|
|---------------------------------------------------|---------------------|----------------------------------|
| `finance/` | GET/POST/PUT/DELETE | 财务记录 CRUD 操作 |
| `finance/{id}/upload_images/` | POST | 上传财务凭证图片 |
| `finance/{id}/` | DELETE | 删除财务记录(发送邮件通知) |
@@ -84,10 +84,10 @@
---
### 4. **邮件通知 (Email Notice)** - `/api/`
### 4. **邮件通知 (Email Notice)**
| 端点 | 方法 | 功能说明 |
|--------------------------|----------|----------------|
|--------------------------|----------|-----------------------------|
| `notification-settings/` | GET/POST | 获取/更新通知设置和邮箱列表 |
| `toggle-email-status/` | POST | 切换邮箱启用/禁用状态 |
@@ -95,10 +95,10 @@
---
### 5. **备忘录 (Memo)** - `/api/`
### 5. **备忘录 (Memo)**
| 端点 | 方法 | 功能说明 |
|----------------------------|---------------------|-------------|
|----------------------------|---------------------|------------------|
| `memos/` | GET/POST/PUT/DELETE | 备忘录 CRUD 操作 |
| `memos/{id}/upload_image/` | POST | 上传备忘录图片 |
| `memos/{id}/delete_image/` | DELETE | 删除备忘录图片 |
@@ -108,10 +108,10 @@
---
### 6. **考评系统 (Evaluation)** - `/api/`
### 6. **考评系统 (Evaluation)**
| 端点 | 方法 | 功能说明 |
|-----------------------------------------|---------------------|------------------------|
|-----------------------------------------|---------------------|--------------------------------------------|
| `evaluation-records/` | GET/POST/PUT/DELETE | 考评记录 CRUD 操作 |
| `evaluation-records/personnel-summary/` | GET | 获取人员考评汇总(按人员、部门、年级分组) |
| `evaluation-records/personnel-records/` | GET | 获取某个人员的所有考评记录 |
@@ -121,6 +121,7 @@
| `evaluation-records/import/` | POST | 导入考评记录(支持 Excel/CSV) |
**功能**:
- 支持批量导入和导出
- 样表格式:只需部门、年级、姓名(初始分39分)
- 完整格式:包含详细的加分/扣分记录
@@ -130,17 +131,56 @@
### 7. **认证相关**
这是原先的 Django REST Framework JWT 认证端点,主要用于获取和刷新 JWT 令牌。
现在改用 `/api/v1/auth/` 端点来处理登录、注册、重置密码等操作。
| 端点 | 方法 | 功能说明 |
|----------------------|----------|-------------|
|----------------------|----------|-----------------|
| `api/token/` | POST | JWT 令牌获取 |
| `api/token/refresh/` | POST | JWT 令牌刷新 |
| `admin/` | GET/POST | Django 管理后台 |
---
## 认证方式
#### 认证方式
所有 API 均使用 **JWT 认证**(除了需要特殊处理的端点):
```
Authorization: Bearer <token>
```
## 数据库结构
[schema.sql](./schema.sql)。
- users (系统用户/认证信息)
- departments (组织/部门)
- parent_id -> departments.id (自关联父级)
- personnel (人员信息)
- user_id -> users.id
- department_id -> departments.id
- categories (物品类别)
- parent_id -> categories.id (自关联父级)
- items (物品基本信息)
- category_id -> categories.id
- created_by -> personnel.id
- item_transactions (物品出入库流水)
- item_id -> items.id (ON DELETE CASCADE)
- operator_id -> personnel.id
- borrow_records (借用/归还记录)
- item_id -> items.id (ON DELETE CASCADE)
- borrower_id -> personnel.id (ON DELETE CASCADE)
- approver_id -> personnel.id
- memos (备忘/备注)
- author_id -> personnel.id
- related_item_id -> items.id
- finance_records (财务相关记录)
- related_item_id -> items.id
- recorded_by -> personnel.id
- email_notices (邮件通知规则/历史)
- evaluations (物品/服务评价)
- related_item_id -> items.id
- evaluator_id -> personnel.id
- attachments (文件附件存储索引表)
- uploaded_by -> personnel.id
+228
View File
@@ -0,0 +1,228 @@
-- 用户表(若项目使用 Django auth,可忽略或与 auth_user 对应)
DROP TABLE IF EXISTS account;
CREATE TABLE account
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(150) NOT NULL UNIQUE,
password VARCHAR(128) NOT NULL,
email VARCHAR(254),
full_name VARCHAR(200),
is_active TINYINT(1) NOT NULL DEFAULT 1,
is_staff TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='系统用户/认证信息';
-- 部门表
DROP TABLE IF EXISTS departments;
CREATE TABLE departments
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
code VARCHAR(50),
parent_id BIGINT UNSIGNED,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_departments_parent FOREIGN KEY (parent_id) REFERENCES departments (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='组织/部门';
-- 人员表(可映射到 Django 的 profile)
DROP TABLE IF EXISTS personnel;
CREATE TABLE personnel
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED, -- 关联 account 表(可为空)
employee_no VARCHAR(64) UNIQUE,
name VARCHAR(100) NOT NULL,
department_id BIGINT UNSIGNED,
phone VARCHAR(50),
email VARCHAR(254),
role VARCHAR(100),
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_personnel_user FOREIGN KEY (user_id) REFERENCES account (id) ON DELETE SET NULL,
CONSTRAINT fk_personnel_department FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='人员信息';
-- 物品类别
DROP TABLE IF EXISTS categories;
CREATE TABLE categories
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(150) NOT NULL,
parent_id BIGINT UNSIGNED,
description TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_categories_parent FOREIGN KEY (parent_id) REFERENCES categories (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品类别';
-- 物品(物品主表)
DROP TABLE IF EXISTS items;
CREATE TABLE items
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(100) NOT NULL UNIQUE, -- 物品编号/资产编号
name VARCHAR(200) NOT NULL,
category_id BIGINT UNSIGNED,
model VARCHAR(150),
brand VARCHAR(150),
location VARCHAR(200),
total_quantity INT NOT NULL DEFAULT 0,
available_quantity INT NOT NULL DEFAULT 0,
unit VARCHAR(30) DEFAULT '件',
price DECIMAL(12, 2) DEFAULT NULL,
purchase_date DATE DEFAULT NULL,
supplier VARCHAR(200) DEFAULT NULL,
status VARCHAR(50) DEFAULT 'normal', -- 如 normal/disabled/lost/repair
description TEXT,
image_path VARCHAR(500),
created_by BIGINT UNSIGNED,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_items_category FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE SET NULL,
CONSTRAINT fk_items_creator FOREIGN KEY (created_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (name),
INDEX (code)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品基本信息';
-- 物品库存/流水(每次入库/出库/调整都记录)
DROP TABLE IF EXISTS item_transactions;
CREATE TABLE item_transactions
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL,
trans_type ENUM ('IN','OUT','ADJUST','TRANSFER') NOT NULL,
quantity INT NOT NULL,
from_location VARCHAR(200),
to_location VARCHAR(200),
related_id BIGINT UNSIGNED, -- 可关联采购单/借用记录/调拨单 id
operator_id BIGINT UNSIGNED, -- personnel 操作者
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_trans_item FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
CONSTRAINT fk_trans_operator FOREIGN KEY (operator_id) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (item_id),
INDEX (trans_type)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品出入库与调整流水';
-- 借用记录(借/还)
DROP TABLE IF EXISTS borrow_records;
CREATE TABLE borrow_records
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
item_id BIGINT UNSIGNED NOT NULL,
borrower_id BIGINT UNSIGNED NOT NULL, -- personnel id
quantity INT NOT NULL DEFAULT 1,
borrow_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
due_date DATETIME DEFAULT NULL,
return_date DATETIME DEFAULT NULL,
status ENUM ('BORROWED','RETURNED','OVERDUE','CANCELED') NOT NULL DEFAULT 'BORROWED',
approver_id BIGINT UNSIGNED DEFAULT NULL,
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_borrow_item FOREIGN KEY (item_id) REFERENCES items (id) ON DELETE CASCADE,
CONSTRAINT fk_borrow_borrower FOREIGN KEY (borrower_id) REFERENCES personnel (id) ON DELETE CASCADE,
CONSTRAINT fk_borrow_approver FOREIGN KEY (approver_id) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (borrower_id),
INDEX (status)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='借用/归还记录';
-- 备忘/便签
DROP TABLE IF EXISTS memos;
CREATE TABLE memos
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
author_id BIGINT UNSIGNED,
related_item_id BIGINT UNSIGNED,
is_public TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_memo_author FOREIGN KEY (author_id) REFERENCES personnel (id) ON DELETE SET NULL,
CONSTRAINT fk_memo_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='备忘/备注';
-- 财务记录(采购/报销等)
DROP TABLE IF EXISTS finance_records;
CREATE TABLE finance_records
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
record_no VARCHAR(100) UNIQUE,
type ENUM ('PURCHASE','REFUND','REPAIR','DEPRECIATION','OTHER') NOT NULL,
related_item_id BIGINT UNSIGNED,
amount DECIMAL(14, 2) NOT NULL DEFAULT 0.00,
currency VARCHAR(10) DEFAULT 'CNY',
vendor VARCHAR(200),
occurred_at DATETIME,
recorded_by BIGINT UNSIGNED,
note TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_finance_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL,
CONSTRAINT fk_finance_user FOREIGN KEY (recorded_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (record_no)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='财务相关记录';
-- 邮件通知规则/历史
DROP TABLE IF EXISTS email_notices;
CREATE TABLE email_notices
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200),
target_emails TEXT, -- 以逗号分隔或 JSON 数组
subject VARCHAR(300),
body TEXT,
enabled TINYINT(1) NOT NULL DEFAULT 1,
schedule_cron VARCHAR(100), -- 若使用定时任务
last_sent_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='邮件通知规则/历史';
-- 评价/反馈(evaluation 模块对应)
DROP TABLE IF EXISTS evaluations;
CREATE TABLE evaluations
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
related_item_id BIGINT UNSIGNED,
evaluator_id BIGINT UNSIGNED,
score TINYINT UNSIGNED, -- 1-5
comment TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_evaluation_item FOREIGN KEY (related_item_id) REFERENCES items (id) ON DELETE SET NULL,
CONSTRAINT fk_evaluation_user FOREIGN KEY (evaluator_id) REFERENCES personnel (id) ON DELETE SET NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='物品/服务评价';
-- 附件表(文件存储路径)
DROP TABLE IF EXISTS attachments;
CREATE TABLE attachments
(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
related_table VARCHAR(100), -- 指示关联表,如 'items','memos'
related_id BIGINT UNSIGNED, -- 关联记录 ID
file_name VARCHAR(300),
file_path VARCHAR(1000),
file_size BIGINT UNSIGNED,
content_type VARCHAR(200),
uploaded_by BIGINT UNSIGNED,
uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_attachments_user FOREIGN KEY (uploaded_by) REFERENCES personnel (id) ON DELETE SET NULL,
INDEX (related_table, related_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='文件附件存储索引表';
@@ -1,56 +1,39 @@
package club.itstudio.itmanager.entity
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.isAccessible
interface DataCopy {
fun <T : Any> toAnotherObject(
toClass: KClass<T>,
otherProperties: Map<String, Any?>
otherProperties: Map<String, Any?> = emptyMap()
): T {
try {
val voProperties = toClass.declaredMemberProperties
@Suppress("UNCHECKED_CAST")
val sourceProps = this::class.memberProperties
.associate { it.name to (it as KProperty1<Any, *>).get(this) }
return try {
val vo = toClass.createInstance()
for (voProperty in voProperties)
copyFieldData(voProperty, otherProperties, vo)
return vo
for (prop in toClass.declaredMemberProperties) {
@Suppress("UNCHECKED_CAST")
val mutableProp = prop as? KMutableProperty1<Any, Any?> ?: continue
val value = when {
sourceProps.containsKey(prop.name) -> sourceProps[prop.name]
otherProperties.containsKey(prop.name) -> otherProperties[prop.name]
else -> continue
}
mutableProp.isAccessible = true
mutableProp.set(vo, value)
}
vo
} catch (e: ReflectiveOperationException) {
throw RuntimeException(
"${toClass.simpleName} has no fields.",
e
"Failed to instantiate ${toClass.simpleName}. Does it have a no-arg constructor?", e
)
}
}
private fun copyFieldData(
toProperty: KProperty<*>,
otherProperties: Map<String, Any?>,
another: Any
) {
try {
val thisProperty = this::class.memberProperties
.firstOrNull { it.name == toProperty.name }
val value =
if (thisProperty != null) {
thisProperty.getter.call(this)
} else if (otherProperties.containsKey(toProperty.name))
otherProperties[toProperty.name]
else
throw RuntimeException(
"Property ${toProperty.name} not found."
)
val field = toProperty.javaField!!
field.isAccessible = true
field.set(another, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -11,10 +11,13 @@ import kotlinx.serialization.Serializable
@TableName("account")
data class Account(
@TableId(type = IdType.AUTO)
val id: Int? = null,
val id: Long? = null,
val username: String = "",
val password: String = "",
val email: String = "",
val role: String = "",
val registerTime: LocalDateTime? = null
val email: String? = null,
val fullName: String? = null,
val isActive: Boolean = true,
val isStaff: Boolean = false,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,23 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("attachments")
data class Attachment(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val relatedTable: String? = null,
val relatedId: Long? = null,
val fileName: String? = null,
val filePath: String? = null,
val fileSize: Long? = null,
val contentType: String? = null,
val uploadedBy: Long? = null,
val uploadedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,26 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("borrow_records")
data class BorrowRecord(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
val borrowerId: Long = 0,
val quantity: Int = 1,
val borrowDate: LocalDateTime? = null,
val dueDate: LocalDateTime? = null,
val returnDate: LocalDateTime? = null,
val status: String = "BORROWED", // BORROWED / RETURNED / OVERDUE / CANCELED
val approverId: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,20 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("categories")
data class Category(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String = "",
val parentId: Long? = null,
val description: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,21 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("departments")
data class Department(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String = "",
val code: String? = null,
val parentId: Long? = null,
val description: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,24 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("email_notices")
data class EmailNotice(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val name: String? = null,
val targetEmails: String? = null,
val subject: String? = null,
val body: String? = null,
val enabled: Boolean = true,
val scheduleCron: String? = null,
val lastSentAt: LocalDateTime? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,20 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("evaluations")
data class Evaluation(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val relatedItemId: Long? = null,
val evaluatorId: Long? = null,
val score: Short? = null, // 1-5
val comment: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,26 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
@TableName("finance_records")
data class FinanceRecord(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val recordNo: String? = null,
val type: String = "", // PURCHASE / REFUND / REPAIR / DEPRECIATION / OTHER
val relatedItemId: Long? = null,
val amount: BigDecimal = BigDecimal.ZERO,
val currency: String = "CNY",
val vendor: String? = null,
val occurredAt: LocalDateTime? = null,
val recordedBy: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,35 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import java.math.BigDecimal
@Serializable
@TableName("items")
data class Item(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val code: String = "",
val name: String = "",
val categoryId: Long? = null,
val model: String? = null,
val brand: String? = null,
val location: String? = null,
val totalQuantity: Int = 0,
val availableQuantity: Int = 0,
val unit: String = "件",
val price: BigDecimal? = null,
val purchaseDate: LocalDate? = null,
val supplier: String? = null,
val status: String = "normal",
val description: String? = null,
val imagePath: String? = null,
val createdBy: Long? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,24 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("item_transactions")
data class ItemTransaction(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
val transType: String = "", // IN / OUT / ADJUST / TRANSFER
val quantity: Int = 0,
val fromLocation: String? = null,
val toLocation: String? = null,
val relatedId: Long? = null,
val operatorId: Long? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,22 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("memos")
data class Memo(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val title: String? = null,
val content: String? = null,
val authorId: Long? = null,
val relatedItemId: Long? = null,
val isPublic: Boolean = false,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,25 @@
package club.itstudio.itmanager.entity.dto
import com.baomidou.mybatisplus.annotation.IdType
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import club.itstudio.itmanager.entity.DataCopy
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@Serializable
@TableName("personnel")
data class Personnel(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val userId: Long? = null,
val employeeNo: String? = null,
val name: String = "",
val departmentId: Long? = null,
val phone: String? = null,
val email: String? = null,
val role: String? = null,
val isActive: Boolean = true,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
) : DataCopy
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Attachment
import org.apache.ibatis.annotations.Mapper
@Mapper
interface AttachmentMapper : BaseMapper<Attachment>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.BorrowRecord
import org.apache.ibatis.annotations.Mapper
@Mapper
interface BorrowRecordMapper : BaseMapper<BorrowRecord>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Category
import org.apache.ibatis.annotations.Mapper
@Mapper
interface CategoryMapper : BaseMapper<Category>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Department
import org.apache.ibatis.annotations.Mapper
@Mapper
interface DepartmentMapper : BaseMapper<Department>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.EmailNotice
import org.apache.ibatis.annotations.Mapper
@Mapper
interface EmailNoticeMapper : BaseMapper<EmailNotice>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Evaluation
import org.apache.ibatis.annotations.Mapper
@Mapper
interface EvaluationMapper : BaseMapper<Evaluation>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.FinanceRecord
import org.apache.ibatis.annotations.Mapper
@Mapper
interface FinanceRecordMapper : BaseMapper<FinanceRecord>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Item
import org.apache.ibatis.annotations.Mapper
@Mapper
interface ItemMapper : BaseMapper<Item>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.ItemTransaction
import org.apache.ibatis.annotations.Mapper
@Mapper
interface ItemTransactionMapper : BaseMapper<ItemTransaction>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Memo
import org.apache.ibatis.annotations.Mapper
@Mapper
interface MemoMapper : BaseMapper<Memo>
@@ -0,0 +1,8 @@
package club.itstudio.itmanager.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import club.itstudio.itmanager.entity.dto.Personnel
import org.apache.ibatis.annotations.Mapper
@Mapper
interface PersonnelMapper : BaseMapper<Personnel>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Attachment
interface AttachmentService : IService<Attachment>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.BorrowRecord
interface BorrowRecordService : IService<BorrowRecord>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Category
interface CategoryService : IService<Category>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Department
interface DepartmentService : IService<Department>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.EmailNotice
interface EmailNoticeService : IService<EmailNotice>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Evaluation
interface EvaluationService : IService<Evaluation>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.FinanceRecord
interface FinanceRecordService : IService<FinanceRecord>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Item
interface ItemService : IService<Item>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.ItemTransaction
interface ItemTransactionService : IService<ItemTransaction>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Memo
interface MemoService : IService<Memo>
@@ -0,0 +1,6 @@
package club.itstudio.itmanager.service
import com.baomidou.mybatisplus.extension.service.IService
import club.itstudio.itmanager.entity.dto.Personnel
interface PersonnelService : IService<Personnel>
@@ -10,7 +10,7 @@ import club.itstudio.itmanager.entity.vo.response.ReloginVO
import club.itstudio.itmanager.mapper.AccountMapper
import club.itstudio.itmanager.service.AccountService
import club.itstudio.itmanager.utils.Const
import club.itstudio.itmanager.utils.DateUtils.getCurrentDateTime
import club.itstudio.itmanager.utils.FlowUtils
import club.itstudio.itmanager.utils.JwtUtils
import org.springframework.amqp.core.AmqpTemplate
@@ -31,7 +31,6 @@ class AccountServiceImpl(
private val amqpTemplate: AmqpTemplate,
private val stringRedisTemplate: ReactiveStringRedisTemplate,
private val encoder: PasswordEncoder
) : ServiceImpl<AccountMapper, Account>(), AccountService {
override fun findByUsername(username: String): Mono<UserDetails> {
@@ -40,7 +39,7 @@ class AccountServiceImpl(
.map { account ->
User.withUsername(username)
.password(account.password)
.roles(account.role)
.roles(if (account.isStaff) "STAFF" else "USER")
.build()
}
}
@@ -82,7 +81,7 @@ class AccountServiceImpl(
val (email, code, username, password) = vo
return verifyCode(email, code).flatMap { error ->
if (!error.isBlank()) {
if (error.isNotBlank()) {
return@flatMap Mono.just(error)
}
@@ -98,12 +97,11 @@ class AccountServiceImpl(
val encodedPassword = encoder.encode(password)
val account = Account(
null,
username,
encodedPassword!!,
email,
"user",
getCurrentDateTime()
username = username,
password = encodedPassword!!,
email = email,
isActive = true,
isStaff = false
)
Mono.fromCallable { this.save(account) }
@@ -124,7 +122,7 @@ class AccountServiceImpl(
val (email, code, password) = vo
return verifyCode(email, code).flatMap { error ->
if (!error.isBlank()) {
if (error.isNotBlank()) {
return@flatMap Mono.just(error)
}
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Attachment
import club.itstudio.itmanager.mapper.AttachmentMapper
import club.itstudio.itmanager.service.AttachmentService
import org.springframework.stereotype.Service
@Service
class AttachmentServiceImpl : ServiceImpl<AttachmentMapper, Attachment>(), AttachmentService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.BorrowRecord
import club.itstudio.itmanager.mapper.BorrowRecordMapper
import club.itstudio.itmanager.service.BorrowRecordService
import org.springframework.stereotype.Service
@Service
class BorrowRecordServiceImpl : ServiceImpl<BorrowRecordMapper, BorrowRecord>(), BorrowRecordService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Category
import club.itstudio.itmanager.mapper.CategoryMapper
import club.itstudio.itmanager.service.CategoryService
import org.springframework.stereotype.Service
@Service
class CategoryServiceImpl : ServiceImpl<CategoryMapper, Category>(), CategoryService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Department
import club.itstudio.itmanager.mapper.DepartmentMapper
import club.itstudio.itmanager.service.DepartmentService
import org.springframework.stereotype.Service
@Service
class DepartmentServiceImpl : ServiceImpl<DepartmentMapper, Department>(), DepartmentService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.EmailNotice
import club.itstudio.itmanager.mapper.EmailNoticeMapper
import club.itstudio.itmanager.service.EmailNoticeService
import org.springframework.stereotype.Service
@Service
class EmailNoticeServiceImpl : ServiceImpl<EmailNoticeMapper, EmailNotice>(), EmailNoticeService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Evaluation
import club.itstudio.itmanager.mapper.EvaluationMapper
import club.itstudio.itmanager.service.EvaluationService
import org.springframework.stereotype.Service
@Service
class EvaluationServiceImpl : ServiceImpl<EvaluationMapper, Evaluation>(), EvaluationService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.FinanceRecord
import club.itstudio.itmanager.mapper.FinanceRecordMapper
import club.itstudio.itmanager.service.FinanceRecordService
import org.springframework.stereotype.Service
@Service
class FinanceRecordServiceImpl : ServiceImpl<FinanceRecordMapper, FinanceRecord>(), FinanceRecordService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Item
import club.itstudio.itmanager.mapper.ItemMapper
import club.itstudio.itmanager.service.ItemService
import org.springframework.stereotype.Service
@Service
class ItemServiceImpl : ServiceImpl<ItemMapper, Item>(), ItemService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.ItemTransaction
import club.itstudio.itmanager.mapper.ItemTransactionMapper
import club.itstudio.itmanager.service.ItemTransactionService
import org.springframework.stereotype.Service
@Service
class ItemTransactionServiceImpl : ServiceImpl<ItemTransactionMapper, ItemTransaction>(), ItemTransactionService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Memo
import club.itstudio.itmanager.mapper.MemoMapper
import club.itstudio.itmanager.service.MemoService
import org.springframework.stereotype.Service
@Service
class MemoServiceImpl : ServiceImpl<MemoMapper, Memo>(), MemoService
@@ -0,0 +1,10 @@
package club.itstudio.itmanager.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import club.itstudio.itmanager.entity.dto.Personnel
import club.itstudio.itmanager.mapper.PersonnelMapper
import club.itstudio.itmanager.service.PersonnelService
import org.springframework.stereotype.Service
@Service
class PersonnelServiceImpl : ServiceImpl<PersonnelMapper, Personnel>(), PersonnelService
@@ -12,5 +12,6 @@ object Const {
const val FLOW_LIMIT_COUNTER = "flow:counter:"
const val FLOW_LIMIT_BLOCK = "flow:block:"
val INTERNAL_ERROR_MONO = Mono.just("something went wrong. Please contact the administrator.")
const val INTERNAL_ERROR_STR = "something went wrong. Please contact the administrator."
val INTERNAL_ERROR_MONO = Mono.just(INTERNAL_ERROR_STR)
}
@@ -4,6 +4,7 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.DecodedJWT
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.security.core.userdetails.User
@@ -16,14 +17,15 @@ import java.util.concurrent.TimeUnit
class JwtUtils(
@param:Value($$"${spring.security.jwt.key}")
private val key: String,
@param:Value($$"${spring.security.jwt.expire-hours}")
private val expireHours: Int,
private val template: StringRedisTemplate,
private val algorithm: Algorithm = Algorithm.HMAC256(key),
private val jwtVerifier: JWTVerifier = JWT.require(algorithm).build()
private val template: StringRedisTemplate
) {
private val algorithm: Algorithm = Algorithm.HMAC256(key)
private val jwtVerifier: JWTVerifier = JWT.require(algorithm).build()
private val logger = LoggerFactory.getLogger(this::class.java)
fun invalidateJwt(headerToken: String?): Boolean {
val token = this.convertToToken(headerToken) ?: return false
try {
@@ -31,7 +33,7 @@ class JwtUtils(
val id = jwt.id
return deleteToken(id, jwt.expiresAt)
} catch (e: Exception) {
println(e.message)
logger.error("Error occurred while invalidating JWT", e)
return false
}
}
@@ -62,14 +64,14 @@ class JwtUtils(
else decodedJWT
} catch (e: Exception) {
println(e.message)
logger.error("Error occurred while resolving JWT", e)
return null
}
}
fun createJwt(
details: UserDetails,
id: Int,
id: Long,
username: String
): String {
return JWT.create()
@@ -94,7 +96,7 @@ class JwtUtils(
.build()
}
fun toId(jwt: DecodedJWT) = jwt.claims["id"]?.asInt() ?: -1
fun toId(jwt: DecodedJWT) = jwt.claims["id"]?.asLong() ?: -1L
fun expiresTime(): Date {
val cal = Calendar.getInstance()