diff --git a/.idea/itstudio_manager.iml b/.idea/itstudio_manager.iml
index 0b1adc5..dc8e515 100644
--- a/.idea/itstudio_manager.iml
+++ b/.idea/itstudio_manager.iml
@@ -5,6 +5,7 @@
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 9576457..a85f383 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -7,11 +7,11 @@
-
-
+
+
-
+
\ No newline at end of file
diff --git a/API.md b/API.md
index 4fe00a0..cc1ad0d 100644
--- a/API.md
+++ b/API.md
@@ -18,6 +18,7 @@ class Response {
- 除特别标注(公开)外,接口需在请求头携带 `Authorization: Bearer `
- 下文路径均省略 `/api` 前缀,`data` 仅给出 `T` 的声明
- 部分 `/test` 接口的返回值不遵循上述格式
+- 前端类示例使用 **TypeScript** 类定义
## 接口总览
diff --git a/README.md b/README.md
index a5c768e..3c80a68 100644
--- a/README.md
+++ b/README.md
@@ -17,15 +17,10 @@
- /reset
- /departments
- /email
- - /evaluation-records
- /finance
- /items
- - /memos
- - /personnel
- - /project-groups
- /proof-images
- /usages
- - /users
## 预计完成的 API 列表
@@ -51,24 +46,6 @@
---
-### 2. **人员管理 (Personnel)**
-
-| 端点 | 方法 | 功能说明 |
-|---------------------------------|---------------------|----------------------------------|
-| `personnel/` | GET/POST/PUT/DELETE | 人员信息 CRUD 操作 |
-| `personnel/statistics/` | GET | 获取人员统计信息(按部门和职位) |
-| `personnel/{id}/set_inactive/` | POST | 设置人员为已卸任状态 |
-| `personnel/{id}/set_active/` | POST | 设置人员为在职状态 |
-| `personnel/{id}/` | DELETE | 删除人员(发送邮件通知) |
-| `personnel/check_expired/` | POST | 检查并更新已到期的人员状态 |
-| `project-groups/` | GET/POST/PUT/DELETE | 项目组 CRUD 操作 |
-| `project-groups/by_department/` | GET | 按部门获取项目组 |
-| `project-groups/{id}/` | DELETE | 删除项目组(发送邮件通知) |
-
-**搜索/过滤字段**: `name`, `student_id`, `phone`, `email`, `grade_major`
-
----
-
### 3. **财务管理 (Finance)**
| 端点 | 方法 | 功能说明 |
@@ -95,41 +72,7 @@
---
-### 5. **备忘录 (Memo)**
-
-| 端点 | 方法 | 功能说明 |
-|----------------------------|---------------------|------------------|
-| `memos/` | GET/POST/PUT/DELETE | 备忘录 CRUD 操作 |
-| `memos/{id}/upload_image/` | POST | 上传备忘录图片 |
-| `memos/{id}/delete_image/` | DELETE | 删除备忘录图片 |
-
-**搜索字段**: `title`, `content`
-**默认排序**: 按更新时间倒序
-
----
-
-### 6. **考评系统 (Evaluation)**
-
-| 端点 | 方法 | 功能说明 |
-|-----------------------------------------|---------------------|--------------------------------------------|
-| `evaluation-records/` | GET/POST/PUT/DELETE | 考评记录 CRUD 操作 |
-| `evaluation-records/personnel-summary/` | GET | 获取人员考评汇总(按人员、部门、年级分组) |
-| `evaluation-records/personnel-records/` | GET | 获取某个人员的所有考评记录 |
-| `evaluation-records/delete-personnel/` | DELETE | 删除某个人员的所有考评记录 |
-| `evaluation-records/export/` | GET | 导出考评记录为 Excel(每个人一个表格) |
-| `evaluation-records/download-template/` | GET | 下载人员导入样表 |
-| `evaluation-records/import/` | POST | 导入考评记录(支持 Excel/CSV) |
-
-**功能**:
-
-- 支持批量导入和导出
-- 样表格式:只需部门、年级、姓名(初始分39分)
-- 完整格式:包含详细的加分/扣分记录
-- 所有操作发送邮件通知
-
----
-
-### 7. **认证相关**
+### 5. **认证相关**
这是原先的 Django REST Framework JWT 认证端点,主要用于获取和刷新 JWT 令牌。
现在改用 `/api/v1/auth/` 端点来处理登录、注册、重置密码等操作。
@@ -154,33 +97,24 @@ Authorization: Bearer
[schema.sql](./schema.sql)
-- users (系统用户/认证信息)
+- users/account (系统用户/认证信息)
- 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
+ - created_by -> account.id
- item_transactions (物品出入库流水)
- item_id -> items.id (ON DELETE CASCADE)
- - operator_id -> personnel.id
+ - operator_id -> account.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
+ - borrower (借入人,字符串)
+ - approver (借出/审批人,字符串)
- finance_records (财务相关记录)
- related_item_id -> items.id
- - recorded_by -> personnel.id
+ - recorded_by -> account.id
- email_notices (邮件通知规则/历史)
-- evaluations (物品/服务评价)
- - related_item_id -> items.id
- - evaluator_id -> personnel.id
- attachments (文件附件存储索引表)
- - uploaded_by -> personnel.id
+ - uploaded_by -> account.id
diff --git a/schema.sql b/schema.sql
index e1ab51f..333d88e 100644
--- a/schema.sql
+++ b/schema.sql
@@ -30,26 +30,6 @@ CREATE TABLE departments
) 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
@@ -84,11 +64,11 @@ CREATE TABLE items
status VARCHAR(50) DEFAULT 'normal', -- 如 normal/disabled/lost/repair
description TEXT,
image_path VARCHAR(500),
- created_by BIGINT UNSIGNED,
+ created_by BIGINT UNSIGNED, -- 关联 account.id
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,
+ CONSTRAINT fk_items_creator FOREIGN KEY (created_by) REFERENCES account (id) ON DELETE SET NULL,
INDEX (name),
INDEX (code)
) ENGINE = InnoDB
@@ -105,11 +85,11 @@ CREATE TABLE item_transactions
from_location VARCHAR(200),
to_location VARCHAR(200),
related_id BIGINT UNSIGNED, -- 可关联采购单/借用记录/调拨单 id
- operator_id BIGINT UNSIGNED, -- personnel 操作者
+ operator_id BIGINT UNSIGNED, -- 关联 account.id(操作者)
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,
+ CONSTRAINT fk_trans_operator FOREIGN KEY (operator_id) REFERENCES account (id) ON DELETE SET NULL,
INDEX (item_id),
INDEX (trans_type)
) ENGINE = InnoDB
@@ -121,41 +101,22 @@ 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
+ borrower VARCHAR(100) NOT NULL, -- 借入人(姓名,字符串)
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,
+ approver VARCHAR(100) 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 (borrower),
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
@@ -168,11 +129,11 @@ CREATE TABLE finance_records
currency VARCHAR(10) DEFAULT 'CNY',
vendor VARCHAR(200),
occurred_at DATETIME,
- recorded_by BIGINT UNSIGNED,
+ recorded_by BIGINT UNSIGNED, -- 关联 account.id(记录人)
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,
+ CONSTRAINT fk_finance_user FOREIGN KEY (recorded_by) REFERENCES account (id) ON DELETE SET NULL,
INDEX (record_no)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='财务相关记录';
@@ -194,35 +155,20 @@ CREATE TABLE email_notices
) 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_table VARCHAR(100), -- 指示关联表,如 'items'
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_by BIGINT UNSIGNED, -- 关联 account.id(上传者)
uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- CONSTRAINT fk_attachments_user FOREIGN KEY (uploaded_by) REFERENCES personnel (id) ON DELETE SET NULL,
+ CONSTRAINT fk_attachments_user FOREIGN KEY (uploaded_by) REFERENCES account (id) ON DELETE SET NULL,
INDEX (related_table, related_id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='文件附件存储索引表';
diff --git a/src/beckend/.gitignore b/src/beckend/.gitignore
index a06264f..344eb0d 100644
--- a/src/beckend/.gitignore
+++ b/src/beckend/.gitignore
@@ -5,6 +5,9 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
+src/main/resources/
+!src/main/resources/application.yaml
+
### STS ###
.apt_generated
.classpath
diff --git a/src/beckend/build.gradle.kts b/src/beckend/build.gradle.kts
index 4181c38..28e3777 100644
--- a/src/beckend/build.gradle.kts
+++ b/src/beckend/build.gradle.kts
@@ -1,7 +1,7 @@
plugins {
- kotlin("jvm") version "2.2.21"
- kotlin("plugin.spring") version "2.2.21"
- id("org.springframework.boot") version "4.0.6"
+ kotlin("jvm") version "2.3.21"
+ kotlin("plugin.spring") version "2.3.21"
+ id("org.springframework.boot") version "4.1.0"
id("io.spring.dependency-management") version "1.1.7"
}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/EvaluationRecordsController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/EvaluationRecordsController.kt
deleted file mode 100644
index 185305f..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/EvaluationRecordsController.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package club.itstudio.itmanager.controller.v1
-
-import club.itstudio.itmanager.utils.Const
-import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.*
-import reactor.core.publisher.Mono
-
-@Validated
-@RestController
-@RequestMapping("/api/v1/evaluation-records")
-class EvaluationRecordsController {
-
- // evaluation-records/ GET/POST/PUT/DELETE 考评记录 CRUD 操作
- @GetMapping("", "/")
- fun getRecords(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PostMapping("", "/")
- fun createRecord(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PutMapping("", "/")
- fun updateRecord(): Mono = Const.INTERNAL_ERROR_MONO
-
- @DeleteMapping("", "/")
- fun deleteRecord(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/personnel-summary/ GET 获取人员考评汇总
- @GetMapping("/personnel-summary", "/personnel-summary/")
- fun getPersonnelSummary(): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/personnel-records/ GET 获取某个人员的所有考评记录
- @GetMapping("/personnel-records", "/personnel-records/")
- fun getPersonnelRecords(@RequestParam(required = false) personId: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/delete-personnel/ DELETE 删除某个人员的所有考评记录
- @DeleteMapping("/delete-personnel", "/delete-personnel/")
- fun deletePersonnelRecords(@RequestParam(required = false) personId: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/export/ GET 导出考评记录为 Excel
- @GetMapping("/export", "/export/")
- fun exportRecords(): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/download-template/ GET 下载人员导入样表
- @GetMapping("/download-template", "/download-template/")
- fun downloadTemplate(): Mono = Const.INTERNAL_ERROR_MONO
-
- // evaluation-records/import/ POST 导入考评记录
- @PostMapping("/import", "/import/")
- fun importRecords(): Mono = Const.INTERNAL_ERROR_MONO
-}
\ No newline at end of file
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ItemsController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ItemsController.kt
index af08c15..5261c29 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ItemsController.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ItemsController.kt
@@ -1,75 +1,178 @@
package club.itstudio.itmanager.controller.v1
+import club.itstudio.itmanager.entity.ApiResponse
+import club.itstudio.itmanager.entity.dto.Attachment
+import club.itstudio.itmanager.entity.dto.Category
+import club.itstudio.itmanager.entity.dto.Item
+import club.itstudio.itmanager.service.v1.ItemsApiService
import club.itstudio.itmanager.utils.Const
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page
+import jakarta.validation.Valid
+import org.slf4j.LoggerFactory
+import org.springframework.http.codec.multipart.FilePart
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
+import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@Validated
@RestController
@RequestMapping("/api/v1/items")
-class ItemsController {
+class ItemsController(
+ private val service: ItemsApiService
+) {
+ private val logger = LoggerFactory.getLogger(this::class.java)
// items/ GET/POST/PUT/DELETE 物品 CRUD 操作
@GetMapping("", "/")
- fun getItems(): Mono = Const.INTERNAL_ERROR_MONO
+ fun getItems(
+ @RequestParam(defaultValue = "1") pageNum: Long,
+ @RequestParam(defaultValue = "10") pageSize: Long,
+ @RequestParam(required = false) keyword: String?,
+ @RequestParam(required = false) status: String?
+ ): Mono> {
+ logger.info("Trying to list items")
+ val page = Page- (pageNum, pageSize)
+ return service.getItems(page, keyword, status)
+ .map { ApiResponse.success(it) }
+ }
@PostMapping("", "/")
- fun createItem(): Mono = Const.INTERNAL_ERROR_MONO
+ fun createItem(@RequestBody @Valid item: Item): Mono> {
+ logger.info("Trying to create item")
+ return service.createItem(item)
+ .map(Const::messageHandler)
+ }
@PutMapping("", "/")
- fun updateItem(): Mono = Const.INTERNAL_ERROR_MONO
+ fun updateItem(@RequestBody @Valid item: Item): Mono> {
+ logger.info("Trying to update item")
+ return service.updateItem(item)
+ .map(Const::messageHandler)
+ }
@DeleteMapping("", "/")
- fun deleteItem(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
+ fun deleteItem(@RequestParam(required = false) id: Long?): Mono> {
+ logger.info("Trying to delete item")
+ return service.deleteItem(id)
+ .map(Const::messageHandler)
+ }
// items/{id}/upload_images/ POST 上传物品图片
@PostMapping("/{id}/upload_images", "/{id}/upload_images/")
- fun uploadImages(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
+ fun uploadImages(
+ @PathVariable("id") id: Long,
+ @RequestPart("files") files: Flux
+ ): Mono> {
+ logger.info("Trying to upload images for item {}", id)
+ return service.uploadImages(id, files)
+ .map(Const::messageHandler)
+ }
// items/{id}/set_primary_image/ POST 设置主图片
@PostMapping("/{id}/set_primary_image", "/{id}/set_primary_image/")
- fun setPrimaryImage(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
+ fun setPrimaryImage(
+ @PathVariable("id") id: Long,
+ @RequestParam imageId: Long
+ ): Mono> {
+ logger.info("Trying to set primary image for item {}", id)
+ return service.setPrimaryImage(id, imageId)
+ .map(Const::messageHandler)
+ }
// items/{id}/borrow/ POST 借用物品
@PostMapping("/{id}/borrow", "/{id}/borrow/")
- fun borrowItem(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
+ fun borrowItem(
+ @PathVariable("id") id: Long,
+ @RequestParam(defaultValue = "1") quantity: Int
+ ): Mono> {
+ logger.info("Trying to borrow item {}", id)
+ return service.borrowItem(id, quantity)
+ .map(Const::messageHandler)
+ }
// items/{id}/return_item/ POST 归还物品
@PostMapping("/{id}/return_item", "/{id}/return_item/")
- fun returnItem(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
+ fun returnItem(
+ @PathVariable("id") id: Long,
+ @RequestParam(defaultValue = "1") quantity: Int
+ ): Mono> {
+ logger.info("Trying to return item {}", id)
+ return service.returnItem(id, quantity)
+ .map(Const::messageHandler)
+ }
// items/available/ GET 获取可用物品列表
@GetMapping("/available", "/available/")
- fun getAvailableItems(): Mono = Const.INTERNAL_ERROR_MONO
+ fun getAvailableItems(): Mono> {
+ logger.info("Trying to list available items")
+ return service.getAvailableItems()
+ .map { ApiResponse.success(it) }
+ }
// items/in_use/ GET 获取使用中的物品列表
@GetMapping("/in_use", "/in_use/")
- fun getInUseItems(): Mono = Const.INTERNAL_ERROR_MONO
+ fun getInUseItems(): Mono> {
+ logger.info("Trying to list in-use items")
+ return service.getInUseItems()
+ .map { ApiResponse.success(it) }
+ }
// items/categories/ GET/POST/PUT/DELETE 物品类别 CRUD 操作
@GetMapping("/categories", "/categories/")
- fun getItemCategories(): Mono = Const.INTERNAL_ERROR_MONO
+ fun getItemCategories(): Mono> {
+ logger.info("Trying to list item categories")
+ return service.getItemCategories()
+ .map { ApiResponse.success(it) }
+ }
@PostMapping("/categories", "/categories/")
- fun createItemCategory(): Mono = Const.INTERNAL_ERROR_MONO
+ fun createItemCategory(@RequestBody @Valid category: Category): Mono> {
+ logger.info("Trying to create item category")
+ return service.createItemCategory(category)
+ .map(Const::messageHandler)
+ }
@PutMapping("/categories", "/categories/")
- fun updateItemCategory(): Mono = Const.INTERNAL_ERROR_MONO
+ fun updateItemCategory(@RequestBody @Valid category: Category): Mono> {
+ logger.info("Trying to update item category")
+ return service.updateItemCategory(category)
+ .map(Const::messageHandler)
+ }
@DeleteMapping("/categories", "/categories/")
- fun deleteItemCategory(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
+ fun deleteItemCategory(@RequestParam(required = false) id: Long?): Mono> {
+ logger.info("Trying to delete item category")
+ return service.deleteItemCategory(id)
+ .map(Const::messageHandler)
+ }
// items/images/ GET/POST/PUT/DELETE 物品图片 CRUD 操作
@GetMapping("/images", "/images/")
- fun getItemImages(): Mono = Const.INTERNAL_ERROR_MONO
+ fun getItemImages(): Mono> {
+ logger.info("Trying to list item images")
+ return service.getItemImages()
+ .map { ApiResponse.success(it) }
+ }
@PostMapping("/images", "/images/")
- fun createItemImage(): Mono = Const.INTERNAL_ERROR_MONO
+ fun createItemImage(@RequestBody @Valid attachment: Attachment): Mono> {
+ logger.info("Trying to create item image")
+ return service.createItemImage(attachment)
+ .map(Const::messageHandler)
+ }
@PutMapping("/images", "/images/")
- fun updateItemImage(): Mono = Const.INTERNAL_ERROR_MONO
+ fun updateItemImage(@RequestBody @Valid attachment: Attachment): Mono> {
+ logger.info("Trying to update item image")
+ return service.updateItemImage(attachment)
+ .map(Const::messageHandler)
+ }
@DeleteMapping("/images", "/images/")
- fun deleteItemImage(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
+ fun deleteItemImage(@RequestParam(required = false) id: Long?): Mono> {
+ logger.info("Trying to delete item image")
+ return service.deleteItemImage(id)
+ .map(Const::messageHandler)
+ }
}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/MemosController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/MemosController.kt
deleted file mode 100644
index a849515..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/MemosController.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package club.itstudio.itmanager.controller.v1
-
-import club.itstudio.itmanager.utils.Const
-import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.*
-import reactor.core.publisher.Mono
-
-@Validated
-@RestController
-@RequestMapping("/api/v1/memos")
-class MemosController {
-
- // memos/ GET/POST/PUT/DELETE 备忘录 CRUD 操作
- @GetMapping("", "/")
- fun getMemos(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PostMapping("", "/")
- fun createMemo(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PutMapping("", "/")
- fun updateMemo(): Mono = Const.INTERNAL_ERROR_MONO
-
- @DeleteMapping("", "/")
- fun deleteMemo(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // memos/{id}/upload_image/ POST 上传备忘录图片
- @PostMapping("/{id}/upload_image", "/{id}/upload_image/")
- fun uploadImage(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-
- // memos/{id}/delete_image/ DELETE 删除备忘录图片
- @DeleteMapping("/{id}/delete_image", "/{id}/delete_image/")
- fun deleteImage(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-}
\ No newline at end of file
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/PersonnelController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/PersonnelController.kt
deleted file mode 100644
index 45ae3e7..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/PersonnelController.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package club.itstudio.itmanager.controller.v1
-
-import club.itstudio.itmanager.utils.Const
-import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.*
-import reactor.core.publisher.Mono
-
-@Validated
-@RestController
-@RequestMapping("/api/v1/personnel")
-class PersonnelController {
-
- // personnel/ GET/POST/PUT/DELETE 人员信息 CRUD 操作
- @GetMapping("", "/")
- fun getPersonnel(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PostMapping("", "/")
- fun createPersonnel(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PutMapping("", "/")
- fun updatePersonnel(): Mono = Const.INTERNAL_ERROR_MONO
-
- @DeleteMapping("", "/")
- fun deletePersonnel(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // personnel/statistics/ GET 获取人员统计信息(按部门和职位)
- @GetMapping("/statistics", "/statistics/")
- fun getStatistics(): Mono = Const.INTERNAL_ERROR_MONO
-
- // personnel/{id}/set_inactive/ POST 设置人员为已卸任状态
- @PostMapping("/{id}/set_inactive", "/{id}/set_inactive/")
- fun setInactive(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-
- // personnel/{id}/set_active/ POST 设置人员为在职状态
- @PostMapping("/{id}/set_active", "/{id}/set_active/")
- fun setActive(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-
- // personnel/{id}/ DELETE 删除人员(发送邮件通知)
- @DeleteMapping("/{id}", "/{id}/")
- fun deleteById(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-
- // personnel/check_expired/ POST 检查并更新已到期的人员状态
- @PostMapping("/check_expired", "/check_expired/")
- fun checkExpired(): Mono = Const.INTERNAL_ERROR_MONO
-}
\ No newline at end of file
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ProjectGroupsController.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ProjectGroupsController.kt
deleted file mode 100644
index e5143b6..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/controller/v1/ProjectGroupsController.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package club.itstudio.itmanager.controller.v1
-
-import club.itstudio.itmanager.utils.Const
-import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.*
-import reactor.core.publisher.Mono
-
-@Validated
-@RestController
-@RequestMapping("/api/v1/project-groups")
-class ProjectGroupsController {
-
- // project-groups/ GET/POST/PUT/DELETE 项目组 CRUD 操作
- @GetMapping("", "/")
- fun getProjectGroups(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PostMapping("", "/")
- fun createProjectGroup(): Mono = Const.INTERNAL_ERROR_MONO
-
- @PutMapping("", "/")
- fun updateProjectGroup(): Mono = Const.INTERNAL_ERROR_MONO
-
- @DeleteMapping("", "/")
- fun deleteProjectGroup(@RequestParam(required = false) id: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // project-groups/by_department/ GET 按部门获取项目组
- @GetMapping("/by_department", "/by_department/")
- fun getByDepartment(@RequestParam(required = false) departmentId: String?): Mono = Const.INTERNAL_ERROR_MONO
-
- // project-groups/{id}/ DELETE 删除项目组(发送邮件通知)
- @DeleteMapping("/{id}", "/{id}/")
- fun deleteById(@PathVariable("id") id: String): Mono = Const.INTERNAL_ERROR_MONO
-}
\ No newline at end of file
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/BorrowRecord.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/BorrowRecord.kt
index a69f1b9..0631f4b 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/BorrowRecord.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/BorrowRecord.kt
@@ -13,13 +13,13 @@ data class BorrowRecord(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
- val borrowerId: Long = 0,
+ val borrower: String = "",
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 approver: String? = null,
val note: String? = null,
val createdAt: LocalDateTime? = null,
val updatedAt: LocalDateTime? = null
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Evaluation.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Evaluation.kt
deleted file mode 100644
index 7abb0b8..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Evaluation.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/ItemTransaction.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/ItemTransaction.kt
index 4fc78ec..b618ab6 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/ItemTransaction.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/ItemTransaction.kt
@@ -4,6 +4,7 @@ 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 club.itstudio.itmanager.entity.enums.TransactionType
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
@@ -13,7 +14,7 @@ data class ItemTransaction(
@TableId(type = IdType.AUTO)
val id: Long? = null,
val itemId: Long = 0,
- val transType: String = "", // IN / OUT / ADJUST / TRANSFER
+ val transType: TransactionType = TransactionType.IN,
val quantity: Int = 0,
val fromLocation: String? = null,
val toLocation: String? = null,
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Memo.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Memo.kt
deleted file mode 100644
index e462f02..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Memo.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Personnel.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Personnel.kt
deleted file mode 100644
index 4dc95c6..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/dto/Personnel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/TransactionType.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/TransactionType.kt
new file mode 100644
index 0000000..1a775e2
--- /dev/null
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/entity/enums/TransactionType.kt
@@ -0,0 +1,19 @@
+package club.itstudio.itmanager.entity.enums
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+enum class TransactionType {
+ @SerialName("IN")
+ IN,
+
+ @SerialName("OUT")
+ OUT,
+
+ @SerialName("ADJUST")
+ ADJUST,
+
+ @SerialName("TRANSFER")
+ TRANSFER,
+}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/EvaluationMapper.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/EvaluationMapper.kt
deleted file mode 100644
index 314a206..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/EvaluationMapper.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/MemoMapper.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/MemoMapper.kt
deleted file mode 100644
index 4943937..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/MemoMapper.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/PersonnelMapper.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/PersonnelMapper.kt
deleted file mode 100644
index 5b3e9dd..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/mapper/PersonnelMapper.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-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
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt
index 2ff8617..b980aa7 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/BorrowRecordService.kt
@@ -7,7 +7,7 @@ import reactor.core.publisher.Mono
interface BorrowRecordService : IService {
fun getBorrowRecordById(id: Long): Mono
fun listByItem(itemId: Long): Mono
>
- fun listByBorrower(borrowerId: Long): Mono>
+ fun listByBorrower(borrower: String): Mono>
fun listByStatus(status: String): Mono>
fun listOverdueRecords(): Mono>
fun createBorrowRecord(record: BorrowRecord): Mono
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt
deleted file mode 100644
index ac1bcb8..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/EvaluationService.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package club.itstudio.itmanager.service
-
-import com.baomidou.mybatisplus.extension.service.IService
-import club.itstudio.itmanager.entity.dto.Evaluation
-import reactor.core.publisher.Mono
-
-interface EvaluationService : IService {
- fun getEvaluationById(id: Long): Mono
- fun listByEvaluator(evaluatorId: Long): Mono>
- fun listByRelatedItem(itemId: Long): Mono>
- fun listByScore(score: Short): Mono>
- fun createEvaluation(evaluation: Evaluation): Mono
- fun updateEvaluation(evaluation: Evaluation): Mono
- fun deleteEvaluation(id: Long): Mono
- fun deleteByEvaluator(evaluatorId: Long): Mono
- fun exportAllEvaluations(): Mono>
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt
deleted file mode 100644
index ab7003c..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/MemoService.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package club.itstudio.itmanager.service
-
-import com.baomidou.mybatisplus.extension.service.IService
-import club.itstudio.itmanager.entity.dto.Memo
-import reactor.core.publisher.Mono
-
-interface MemoService : IService {
- fun getMemoById(id: Long): Mono
- fun searchMemos(keyword: String?): Mono>
- fun listByAuthor(authorId: Long): Mono>
- fun listByRelatedItem(itemId: Long): Mono>
- fun listPublicMemos(): Mono>
- fun createMemo(memo: Memo): Mono
- fun updateMemo(memo: Memo): Mono
- fun deleteMemo(id: Long): Mono
- fun exportAllMemos(): Mono>
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt
deleted file mode 100644
index fa3c44c..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/PersonnelService.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package club.itstudio.itmanager.service
-
-import com.baomidou.mybatisplus.extension.service.IService
-import club.itstudio.itmanager.entity.dto.Personnel
-import reactor.core.publisher.Mono
-
-interface PersonnelService : IService {
- fun getPersonnelById(id: Long): Mono
- fun searchPersonnel(keyword: String?): Mono>
- fun listByDepartment(departmentId: Long): Mono>
- fun listByRole(role: String): Mono>
- fun createPersonnel(personnel: Personnel): Mono
- fun updatePersonnel(personnel: Personnel): Mono
- fun deletePersonnel(id: Long): Mono
- fun setActive(id: Long, active: Boolean): Mono
- fun exportAllPersonnel(): Mono>
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt
index 3dc6e3f..5871a1a 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/BorrowRecordServiceImpl.kt
@@ -30,10 +30,10 @@ class BorrowRecordServiceImpl : ServiceImpl(),
ktQuery().eq(BorrowRecord::itemId, itemId).list()
}
- override fun listByBorrower(borrowerId: Long): Mono> =
+ override fun listByBorrower(borrower: String): Mono> =
blockingCall {
- logger.info("Searching borrow records by borrower id")
- ktQuery().eq(BorrowRecord::borrowerId, borrowerId).list()
+ logger.info("Searching borrow records by borrower")
+ ktQuery().eq(BorrowRecord::borrower, borrower).list()
}
override fun listByStatus(status: String): Mono> =
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt
deleted file mode 100644
index bed43a4..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/EvaluationServiceImpl.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package club.itstudio.itmanager.service.impl
-
-import club.itstudio.itmanager.entity.dto.Evaluation
-import club.itstudio.itmanager.mapper.EvaluationMapper
-import club.itstudio.itmanager.service.EvaluationService
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
-import org.slf4j.LoggerFactory
-import org.springframework.stereotype.Service
-import reactor.core.publisher.Mono
-import reactor.core.scheduler.Schedulers
-import java.util.NoSuchElementException
-
-@Service
-class EvaluationServiceImpl : ServiceImpl(), EvaluationService {
-
- private val logger = LoggerFactory.getLogger(this::class.java)
-
- private fun blockingCall(block: () -> T?): Mono =
- Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic())
-
- override fun getEvaluationById(id: Long): Mono =
- blockingCall {
- logger.info("Searching evaluation by id")
- ktQuery().eq(Evaluation::id, id).one()
- }.switchIfEmpty(Mono.error(NoSuchElementException("Evaluation not found")))
-
- override fun listByEvaluator(evaluatorId: Long): Mono> =
- blockingCall {
- logger.info("Searching evaluations by evaluator id")
- ktQuery().eq(Evaluation::evaluatorId, evaluatorId).list()
- }
-
- override fun listByRelatedItem(itemId: Long): Mono> =
- blockingCall {
- logger.info("Searching evaluations by related item id")
- ktQuery().eq(Evaluation::relatedItemId, itemId).list()
- }
-
- override fun listByScore(score: Short): Mono> =
- blockingCall {
- logger.info("Searching evaluations by score")
- ktQuery().eq(Evaluation::score, score).list()
- }
-
- override fun createEvaluation(evaluation: Evaluation): Mono =
- blockingCall {
- logger.info("Creating evaluation")
- save(evaluation)
- }
-
- override fun updateEvaluation(evaluation: Evaluation): Mono =
- blockingCall {
- logger.info("Updating evaluation")
- updateById(evaluation)
- }
-
- override fun deleteEvaluation(id: Long): Mono =
- blockingCall {
- logger.info("Deleting evaluation by id")
- removeById(id)
- }
-
- override fun deleteByEvaluator(evaluatorId: Long): Mono =
- blockingCall {
- logger.info("Deleting evaluations by evaluator id")
- remove(ktQuery().eq(Evaluation::evaluatorId, evaluatorId))
- }
-
- override fun exportAllEvaluations(): Mono> =
- blockingCall {
- logger.info("Exporting all evaluations")
- list()
- }
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt
deleted file mode 100644
index 2045119..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/MemoServiceImpl.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package club.itstudio.itmanager.service.impl
-
-import club.itstudio.itmanager.entity.dto.Memo
-import club.itstudio.itmanager.mapper.MemoMapper
-import club.itstudio.itmanager.service.MemoService
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
-import org.slf4j.LoggerFactory
-import org.springframework.stereotype.Service
-import reactor.core.publisher.Mono
-import reactor.core.scheduler.Schedulers
-import java.util.NoSuchElementException
-
-@Service
-class MemoServiceImpl : ServiceImpl(), MemoService {
-
- private val logger = LoggerFactory.getLogger(this::class.java)
-
- private fun blockingCall(block: () -> T?): Mono =
- Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic())
-
- override fun getMemoById(id: Long): Mono =
- blockingCall {
- logger.info("Searching memo by id")
- ktQuery().eq(Memo::id, id).one()
- }.switchIfEmpty(Mono.error(NoSuchElementException("Memo not found")))
-
- override fun searchMemos(keyword: String?): Mono> =
- blockingCall {
- logger.info("Searching memos with keyword")
- if (keyword.isNullOrBlank()) {
- list()
- } else {
- ktQuery().and {
- it.like(Memo::title, keyword).or().like(Memo::content, keyword)
- }.list()
- }
- }
-
- override fun listByAuthor(authorId: Long): Mono> =
- blockingCall {
- logger.info("Searching memos by author id")
- ktQuery().eq(Memo::authorId, authorId).list()
- }
-
- override fun listByRelatedItem(itemId: Long): Mono> =
- blockingCall {
- logger.info("Searching memos by related item id")
- ktQuery().eq(Memo::relatedItemId, itemId).list()
- }
-
- override fun listPublicMemos(): Mono> =
- blockingCall {
- logger.info("Listing public memos")
- ktQuery().eq(Memo::isPublic, true).list()
- }
-
- override fun createMemo(memo: Memo): Mono =
- blockingCall {
- logger.info("Creating memo")
- save(memo)
- }
-
- override fun updateMemo(memo: Memo): Mono =
- blockingCall {
- logger.info("Updating memo")
- updateById(memo)
- }
-
- override fun deleteMemo(id: Long): Mono =
- blockingCall {
- logger.info("Deleting memo by id")
- removeById(id)
- }
-
- override fun exportAllMemos(): Mono> =
- blockingCall {
- logger.info("Exporting all memos")
- list()
- }
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt
deleted file mode 100644
index ff1463f..0000000
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/impl/PersonnelServiceImpl.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package club.itstudio.itmanager.service.impl
-
-import club.itstudio.itmanager.entity.dto.Personnel
-import club.itstudio.itmanager.mapper.PersonnelMapper
-import club.itstudio.itmanager.service.PersonnelService
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
-import org.slf4j.LoggerFactory
-import org.springframework.stereotype.Service
-import reactor.core.publisher.Mono
-import reactor.core.scheduler.Schedulers
-import java.util.NoSuchElementException
-
-@Service
-class PersonnelServiceImpl : ServiceImpl(), PersonnelService {
-
- private val logger = LoggerFactory.getLogger(this::class.java)
-
- private fun blockingCall(block: () -> T?): Mono =
- Mono.fromCallable(block).subscribeOn(Schedulers.boundedElastic())
-
- override fun getPersonnelById(id: Long): Mono =
- blockingCall {
- logger.info("Searching personnel by id")
- ktQuery().eq(Personnel::id, id).one()
- }.switchIfEmpty(Mono.error(NoSuchElementException("Personnel not found")))
-
- override fun searchPersonnel(keyword: String?): Mono> =
- blockingCall {
- logger.info("Searching personnel with keyword")
- if (keyword.isNullOrBlank()) {
- list()
- } else {
- ktQuery().and {
- it.like(Personnel::name, keyword)
- .or().like(Personnel::employeeNo, keyword)
- .or().like(Personnel::phone, keyword)
- .or().like(Personnel::email, keyword)
- }.list()
- }
- }
-
- override fun listByDepartment(departmentId: Long): Mono> =
- blockingCall {
- logger.info("Searching personnel by department id")
- ktQuery().eq(Personnel::departmentId, departmentId).list()
- }
-
- override fun listByRole(role: String): Mono> =
- blockingCall {
- logger.info("Searching personnel by role")
- ktQuery().eq(Personnel::role, role).list()
- }
-
- override fun createPersonnel(personnel: Personnel): Mono =
- blockingCall {
- logger.info("Creating personnel")
- save(personnel)
- }
-
- override fun updatePersonnel(personnel: Personnel): Mono =
- blockingCall {
- logger.info("Updating personnel")
- updateById(personnel)
- }
-
- override fun deletePersonnel(id: Long): Mono =
- blockingCall {
- logger.info("Deleting personnel by id")
- removeById(id)
- }
-
- override fun setActive(id: Long, active: Boolean): Mono =
- blockingCall {
- logger.info("Setting personnel active status")
- ktUpdate().eq(Personnel::id, id).set(Personnel::isActive, active).update()
- }
-
- override fun exportAllPersonnel(): Mono> =
- blockingCall {
- logger.info("Exporting all personnel")
- list()
- }
-}
diff --git a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt
index 127d375..6d3bdd4 100644
--- a/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt
+++ b/src/beckend/src/main/kotlin/club/itstudio/itmanager/service/v1/ItemsApiService.kt
@@ -4,6 +4,7 @@ import club.itstudio.itmanager.entity.dto.Attachment
import club.itstudio.itmanager.entity.dto.Category
import club.itstudio.itmanager.entity.dto.Item
import club.itstudio.itmanager.entity.dto.ItemTransaction
+import club.itstudio.itmanager.entity.enums.TransactionType
import club.itstudio.itmanager.service.AttachmentService
import club.itstudio.itmanager.service.CategoryService
import club.itstudio.itmanager.service.ItemService
@@ -15,12 +16,339 @@ import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.codec.multipart.FilePart
import org.springframework.stereotype.Service
+import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Paths
+import java.util.NoSuchElementException
import java.util.UUID
@Service
-class ItemsApiService
\ No newline at end of file
+class ItemsApiService(
+ private val itemService: ItemService,
+ private val categoryService: CategoryService,
+ private val attachmentService: AttachmentService,
+ private val transactionService: ItemTransactionService,
+
+ @Value($$"${app.file.data-path}")
+ private val dataPath: String
+) {
+ private val logger = LoggerFactory.getLogger(this::class.java)
+
+ fun getItems(page: Page- , keyword: String?, status: String?): Mono> {
+ logger.info("Listing items with keyword and status")
+ return itemService.pageItems(page, keyword, status)
+ }
+
+ fun createItem(item: Item): Mono {
+ logger.info("Creating item")
+ val rightNow = getCurrentDateTime()
+ return itemService.createItem(
+ item.copy(
+ id = null,
+ createdAt = rightNow,
+ updatedAt = rightNow
+ )
+ ).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("item create failed.")
+ }
+ }
+
+ fun updateItem(item: Item): Mono {
+ logger.info("Updating item")
+ if (item.id == null) return Mono.just("item id is required.")
+ return itemService.updateItem(
+ item.copy(updatedAt = getCurrentDateTime())
+ ).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("item update failed.")
+ }
+ }
+
+ fun deleteItem(id: Long?): Mono {
+ logger.info("Deleting item with id {}", id)
+ if (id == null) return Mono.just("item id is required.")
+ return itemService.removeItemById(id).flatMap { success ->
+ if (success) {
+ cleanupItemAttachments(id)
+ } else {
+ Mono.just("item delete failed.")
+ }
+ }
+ }
+
+ fun uploadImages(id: Long, files: Flux): Mono {
+ logger.info("Uploading images for item {}", id)
+ return itemService.getItemById(id)
+ .flatMap { item ->
+ files.flatMap { file -> saveItemImage(id, file) }
+ .collectList()
+ .flatMap { attachments ->
+ if (attachments.isEmpty()) {
+ Mono.just("no image files provided.")
+ } else {
+ Flux.fromIterable(attachments)
+ .flatMap { attachmentService.createAttachment(it) }
+ .collectList()
+ .flatMap { results ->
+ if (results.any { !it }) {
+ Mono.just("item image upload failed.")
+ } else {
+ setPrimaryImageIfBlank(item, attachments.first(), id)
+ }
+ }
+ }
+ }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
+ .onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
+ }
+
+ fun setPrimaryImage(id: Long, imageId: Long): Mono {
+ logger.info("Setting primary image for item {}", id)
+ return itemService.getItemById(id)
+ .flatMap { _ ->
+ attachmentService.getAttachmentById(imageId)
+ .flatMap { attachment ->
+ if (attachment.relatedTable == "items" && attachment.relatedId == id) {
+ val path = attachment.filePath
+ if (path.isNullOrBlank()) {
+ Mono.just("image file path is empty.")
+ } else {
+ itemService.setPrimaryImage(id, path).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("set primary image failed.")
+ }
+ }
+ } else {
+ Mono.just("image does not belong to this item.")
+ }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { _ -> Mono.just("image not found.") }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { _ -> Mono.just("item not found.") }
+ .onErrorResume { _ -> Mono.just(Const.INTERNAL_ERROR_STR) }
+ }
+
+ fun borrowItem(id: Long, quantity: Int): Mono {
+ logger.info("Borrowing item {} with quantity {}", id, quantity)
+ if (quantity <= 0) return Mono.just("quantity must be positive.")
+ return itemService.getItemById(id)
+ .flatMap { item ->
+ itemService.borrowItem(id, quantity)
+ .flatMap { success ->
+ if (success) {
+ transactionService.createTransaction(
+ ItemTransaction(
+ itemId = id,
+ transType = TransactionType.OUT,
+ quantity = quantity,
+ fromLocation = item.location,
+ createdAt = getCurrentDateTime()
+ )
+ ).onErrorResume { error ->
+ logger.warn("Failed to record OUT transaction for item {}: {}", id, error.message)
+ Mono.just(false)
+ }.thenReturn("")
+ } else {
+ Mono.just("item available quantity is not enough.")
+ }
+ }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
+ .onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
+ }
+
+ fun returnItem(id: Long, quantity: Int): Mono {
+ logger.info("Returning item {} with quantity {}", id, quantity)
+ if (quantity <= 0) return Mono.just("quantity must be positive.")
+ return itemService.getItemById(id)
+ .flatMap { item ->
+ itemService.returnItem(id, quantity)
+ .flatMap { success ->
+ if (success) {
+ transactionService.createTransaction(
+ ItemTransaction(
+ itemId = id,
+ transType = TransactionType.IN,
+ quantity = quantity,
+ toLocation = item.location,
+ createdAt = getCurrentDateTime()
+ )
+ ).onErrorResume { error ->
+ logger.warn("Failed to record IN transaction for item {}: {}", id, error.message)
+ Mono.just(false)
+ }.thenReturn("")
+ } else {
+ Mono.just("item cannot be returned with the given quantity.")
+ }
+ }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { Mono.just("item not found.") }
+ .onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
+ }
+
+ fun getAvailableItems(): Mono
> {
+ logger.info("Listing available items")
+ return itemService.listAvailableItems()
+ }
+
+ fun getInUseItems(): Mono> {
+ logger.info("Listing in-use items")
+ return itemService.listInUseItems()
+ }
+
+ fun getItemCategories(): Mono> {
+ logger.info("Listing item categories")
+ return categoryService.listAllCategories()
+ }
+
+ fun createItemCategory(category: Category): Mono {
+ logger.info("Creating item category")
+ return categoryService.createCategory(
+ category.copy(
+ id = null,
+ createdAt = getCurrentDateTime(),
+ updatedAt = getCurrentDateTime()
+ )
+ ).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("category create failed.")
+ }
+ }
+
+ fun updateItemCategory(category: Category): Mono {
+ logger.info("Updating item category")
+ if (category.id == null) return Mono.just("category id is required.")
+ return categoryService.updateCategory(
+ category.copy(updatedAt = getCurrentDateTime())
+ ).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("category update failed.")
+ }
+ }
+
+ fun deleteItemCategory(id: Long?): Mono {
+ logger.info("Deleting item category with id {}", id)
+ if (id == null) return Mono.just("category id is required.")
+ return categoryService.deleteCategory(id).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("category delete failed.")
+ }
+ }
+
+ fun getItemImages(): Mono> {
+ logger.info("Listing item images")
+ return attachmentService.exportAllAttachments()
+ .map { list -> list.filter { it.relatedTable == "items" } }
+ }
+
+ fun createItemImage(attachment: Attachment): Mono {
+ logger.info("Creating item image")
+ return attachmentService.createAttachment(
+ attachment.copy(
+ id = null,
+ uploadedAt = getCurrentDateTime()
+ )
+ ).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("item image create failed.")
+ }
+ }
+
+ fun updateItemImage(attachment: Attachment): Mono {
+ logger.info("Updating item image")
+ if (attachment.id == null) return Mono.just("item image id is required.")
+ return Mono.fromCallable { attachmentService.updateById(attachment) }
+ .subscribeOn(Schedulers.boundedElastic())
+ .flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("item image update failed.")
+ }
+ }
+
+ fun deleteItemImage(id: Long?): Mono {
+ logger.info("Deleting item image with id {}", id)
+ if (id == null) return Mono.just("item image id is required.")
+ return attachmentService.getAttachmentById(id)
+ .flatMap { attachment ->
+ attachmentService.deleteAttachment(id).flatMap { success ->
+ if (success) {
+ deletePhysicalFile(attachment.filePath).thenReturn("")
+ } else {
+ Mono.just("item image delete failed.")
+ }
+ }
+ }
+ .onErrorResume(NoSuchElementException::class.java) { Mono.just("item image not found.") }
+ .onErrorResume { Mono.just(Const.INTERNAL_ERROR_STR) }
+ }
+
+ private fun setPrimaryImageIfBlank(item: Item, attachment: Attachment, id: Long): Mono {
+ if (!item.imagePath.isNullOrBlank()) return Mono.just("")
+ val path = attachment.filePath ?: return Mono.just("")
+ return itemService.setPrimaryImage(id, path).flatMap { success ->
+ if (success) Mono.just("")
+ else Mono.just("set primary image failed.")
+ }
+ }
+
+ private fun saveItemImage(itemId: Long, file: FilePart): Mono {
+ val directory = Paths.get(dataPath, Const.FILE_DIR_IMAGE, "items", itemId.toString())
+ val savedName = generateSafeFileName(file.filename())
+ val target = directory.resolve(savedName)
+ return Mono.fromCallable {
+ Files.createDirectories(directory)
+ file.transferTo(target)
+ Attachment(
+ relatedTable = "items",
+ relatedId = itemId,
+ fileName = savedName,
+ filePath = target.toString(),
+ fileSize = Files.size(target),
+ contentType = file.headers().contentType?.toString(),
+ uploadedAt = getCurrentDateTime()
+ )
+ }.subscribeOn(Schedulers.boundedElastic())
+ }
+
+ private fun generateSafeFileName(original: String?): String {
+ val extension = original
+ ?.substringAfterLast('.')
+ ?.lowercase()
+ ?.takeIf {
+ it.isNotBlank() &&
+ it.length <= 10 &&
+ it.all { ch -> ch.isLetterOrDigit() }
+ }
+ return if (extension != null) "${UUID.randomUUID()}.$extension" else UUID.randomUUID().toString()
+ }
+
+ private fun cleanupItemAttachments(itemId: Long): Mono {
+ return attachmentService.listByRelated("items", itemId)
+ .flatMap { attachments ->
+ Flux.fromIterable(attachments)
+ .flatMap { deletePhysicalFile(it.filePath) }
+ .then(attachmentService.deleteByRelated("items", itemId))
+ .thenReturn("")
+ }
+ .onErrorResume { error ->
+ logger.warn("Failed to clean up attachments for item {}: {}", itemId, error.message)
+ Mono.just("")
+ }
+ }
+
+ private fun deletePhysicalFile(path: String?): Mono {
+ if (path.isNullOrBlank()) return Mono.empty()
+ return Mono.fromRunnable {
+ try {
+ Files.deleteIfExists(Paths.get(path))
+ } catch (e: IOException) {
+ logger.warn("Failed to delete file: {}", path, e)
+ }
+ }.subscribeOn(Schedulers.boundedElastic())
+ }
+}
diff --git a/src/beckend/src/main/resources/application.yaml b/src/beckend/src/main/resources/application.yaml
index 8c9ef51..61a2e93 100644
--- a/src/beckend/src/main/resources/application.yaml
+++ b/src/beckend/src/main/resources/application.yaml
@@ -25,17 +25,14 @@ spring:
security:
jwt:
- key: 6846513216
+ #key: 6846513216
expire-hours: 168
datasource:
url: jdbc:mariadb://localhost:3306/kotlin_springboot
driver-class-name: org.mariadb.jdbc.Driver
- username: root
- password: 123456
-
-mybatis-plus:
- type-handlers-package: com.github.sor2171.backend.config
+ #username: root
+ #password: 123456
app:
flow-limit: