feat(wms): add project-funded procurement traceability
This commit is contained in:
parent
79773cc96e
commit
759e2ab545
3
.gitignore
vendored
3
.gitignore
vendored
@ -70,6 +70,9 @@ ruoyi-ui/yarn-error.log*
|
|||||||
/profile/
|
/profile/
|
||||||
*.pid
|
*.pid
|
||||||
|
|
||||||
|
# Local environment overrides may contain database credentials.
|
||||||
|
/ruoyi-admin/src/main/resources/application-test.yml
|
||||||
|
|
||||||
!*/build/*.java
|
!*/build/*.java
|
||||||
!*/build/*.html
|
!*/build/*.html
|
||||||
!*/build/*.xml
|
!*/build/*.xml
|
||||||
|
|||||||
@ -24,3 +24,12 @@ npm run build:prod
|
|||||||
```
|
```
|
||||||
|
|
||||||
后端启动包输出为 `ruoyi-admin/target/cmvr-wms.jar`。
|
后端启动包输出为 `ruoyi-admin/target/cmvr-wms.jar`。
|
||||||
|
|
||||||
|
## 数据库升级
|
||||||
|
|
||||||
|
已有数据库升级前必须先备份目标库,并执行 `select database();` 确认连接库名。按以下顺序执行:
|
||||||
|
|
||||||
|
1. `sql/wms_optimization_20260916.sql`:补充查询索引,并将可唯一匹配的历史人员昵称规范为账号。
|
||||||
|
2. `sql/wms_project_management_20260922.sql`:增加项目、经费预算与流水、采购金额、库存来源批次及跨项目使用能力。
|
||||||
|
|
||||||
|
两个脚本均可重复执行。项目经费脚本会把无法还原真实项目归属的历史采购和库存放入 `LEGACY` 项目,不会虚构原始归属。升级后应按 [采购与项目经费改造说明](docs/purchase-project-refactor.md) 完成业务回归,再发布应用。
|
||||||
|
|||||||
58
docs/purchase-project-refactor.md
Normal file
58
docs/purchase-project-refactor.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# 采购与项目经费改造说明
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
本次改造将采购从独立申请单升级为项目经费闭环:
|
||||||
|
|
||||||
|
`项目分科目预算 -> 采购申请占用 -> 分批到货验收 -> 实际支出 -> 项目来源库存批次 -> 项目领用`
|
||||||
|
|
||||||
|
## 采购主线边界
|
||||||
|
|
||||||
|
- 项目:项目基本信息、负责人、成员及访问范围。
|
||||||
|
- 经费:经费科目、项目分科目预算、占用金额、支出金额和不可变流水。
|
||||||
|
- 采购申请:项目、经费科目、预计含税单价、预计金额及采购状态。
|
||||||
|
- 采购导入:普通 Excel、带下拉选项的模板、表格粘贴和智能识别结果均须补齐项目、科目和预计单价。
|
||||||
|
- 到货验收:支持分批到货、实际含税单价、实际金额、拒收和未入库前撤销验收。
|
||||||
|
- 入库:验收通过生成待入库单;确认入库后生成带来源项目、经费科目和实际成本的库存批次。
|
||||||
|
- 出库:按库存批次扣减,记录来源项目与使用项目;跨项目使用须有权限和原因。
|
||||||
|
|
||||||
|
AI 指令审计、批量导入性能、移动端和仪表盘调整与采购主线共享同一未提交工作区,但不属于采购闭环的最小发布范围,提交时应单独审查。
|
||||||
|
|
||||||
|
## 经费规则
|
||||||
|
|
||||||
|
- 新增采购申请:按 `需求数量 * 预计含税单价` 增加占用金额。
|
||||||
|
- 修改有效申请:按新旧预计金额差额调整占用;更换项目或科目时先释放原占用,再占用新预算。
|
||||||
|
- 取消或删除申请:释放尚未结算的预计占用。
|
||||||
|
- 验收通过:按本批数量对应的预计金额释放占用,并按实际金额增加支出。
|
||||||
|
- 撤销已通过验收:仅允许在对应入库单尚未确认时执行,同时恢复占用并冲回实际支出。
|
||||||
|
|
||||||
|
## 数据库升级
|
||||||
|
|
||||||
|
1. 备份目标数据库。
|
||||||
|
2. 执行 `select database();`,确认连接的是预期数据库。
|
||||||
|
3. 执行 `sql/wms_optimization_20260916.sql`。
|
||||||
|
4. 执行 `sql/wms_project_management_20260922.sql`。
|
||||||
|
5. 再次执行第 3、4 步,确认脚本可安全重放。
|
||||||
|
6. 检查 `project_info`、`expense_category`、`project_budget`、`project_fund_ledger`、`stock_lot` 和 `out_record_lot`。
|
||||||
|
|
||||||
|
## 最小回归清单
|
||||||
|
|
||||||
|
1. 创建进行中的项目,将测试用户加入项目并配置一个经费科目预算。
|
||||||
|
2. 新建采购申请,确认预计金额和项目预算占用同步增加。
|
||||||
|
3. 修改数量或预计单价,确认只产生差额占用流水。
|
||||||
|
4. 登记部分到货并验收通过,确认采购状态为部分到货、占用减少、实际支出增加,并生成待入库单。
|
||||||
|
5. 确认入库,检查库存汇总和 `stock_lot` 的来源项目、科目、采购申请、到货记录及单位成本。
|
||||||
|
6. 对第二批到货验收,确认采购状态变为已到齐。
|
||||||
|
7. 对尚未确认入库的验收执行撤销,确认待入库单删除、经费恢复且采购状态回退。
|
||||||
|
8. 验证预算不足、超量到货、非项目成员、已归档项目和重复提交均被拒绝。
|
||||||
|
|
||||||
|
## 当前验证状态
|
||||||
|
|
||||||
|
- 2026-09-28 在 `wms-test`(MySQL 8.0.36)执行升级前 50 张基础表的数据与结构快照,备份库为 `wms_test_backup_20260928_103302`。
|
||||||
|
- `wms_optimization_20260916.sql` 和 `wms_project_management_20260922.sql` 均连续执行两遍成功,重放后业务表行数保持不变。
|
||||||
|
- 真实 Spring 服务与测试库事务回归通过,测试数据已全部回滚。覆盖:项目预算、采购占用、预计金额调整、部分到货、验收结算、撤销验收、再次验收、确认入库、库存批次成本和采购到齐。
|
||||||
|
- 回归发现并修复了“撤销后再次验收复用旧幂等键,导致经费不重新结算”的问题;验收流水幂等键现按验收周期生成。
|
||||||
|
- 后端 `mvn clean test`:通过,但仓库暂时没有常驻自动化测试源文件。
|
||||||
|
- 前端 `npm run build:prod`:通过,存在包体积告警。
|
||||||
|
- 2026-09-28 发布前已备份 `wms-dev`,备份库为 `wms_dev_backup_20260928_105015`;升级脚本连续执行两遍成功,基础表由 42 张增至 50 张,原业务表行数保持不变。
|
||||||
|
- `wms-dev` 真实 Spring 服务事务回归通过并完成回滚校验,未保留测试项目、采购、到货、入库或库存批次数据。
|
||||||
3
pom.xml
3
pom.xml
@ -191,8 +191,7 @@
|
|||||||
<version>3.13.0</version>
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<parameters>true</parameters>
|
<parameters>true</parameters>
|
||||||
<source>${java.version}</source>
|
<release>${java.version}</release>
|
||||||
<target>${java.version}</target>
|
|
||||||
<encoding>${project.build.sourceEncoding}</encoding>
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|||||||
@ -0,0 +1,147 @@
|
|||||||
|
package com.ruoyi.web.controller.project;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectBudget;
|
||||||
|
import com.ruoyi.project.domain.ProjectFundLedger;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/project/info")
|
||||||
|
public class ProjectInfoController extends BaseController
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectBudgetService projectBudgetService;
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:project:list,project:project:manage')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(ProjectInfo project)
|
||||||
|
{
|
||||||
|
startPage();
|
||||||
|
return getDataTable(projectInfoService.selectProjectInfoList(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:project:query,project:project:list,project:project:manage')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable Long id)
|
||||||
|
{
|
||||||
|
return success(projectInfoService.selectProjectInfoById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:project:list,project:project:query,project:project:manage,project:inventory:use,warehouse:purchase:list,warehouse:purchase:add,warehouse:purchase:edit,warehouse:purchase:import,warehouse:purchase:manage,warehouse:in:list,warehouse:in:add,warehouse:in:edit,warehouse:out:list,warehouse:out:add,warehouse:stock:out')")
|
||||||
|
@GetMapping("/options")
|
||||||
|
public AjaxResult options(String keyword,
|
||||||
|
@RequestParam(defaultValue = "false") boolean includeArchived)
|
||||||
|
{
|
||||||
|
return success(projectInfoService.selectProjectOptions(keyword, includeArchived));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:project:add')")
|
||||||
|
@Log(title = "项目管理", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult add(@RequestBody ProjectInfo project)
|
||||||
|
{
|
||||||
|
return toAjax(projectInfoService.insertProjectInfo(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:project:edit')")
|
||||||
|
@Log(title = "项目管理", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping
|
||||||
|
public AjaxResult edit(@RequestBody ProjectInfo project)
|
||||||
|
{
|
||||||
|
return toAjax(projectInfoService.updateProjectInfo(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:project:remove')")
|
||||||
|
@Log(title = "项目管理", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public AjaxResult remove(@PathVariable Long id)
|
||||||
|
{
|
||||||
|
return toAjax(projectInfoService.deleteProjectInfo(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:budget:list,project:budget:edit,project:project:manage')")
|
||||||
|
@GetMapping("/{projectId}/budgets")
|
||||||
|
public AjaxResult budgets(@PathVariable Long projectId)
|
||||||
|
{
|
||||||
|
return success(projectBudgetService.selectProjectBudgetList(projectId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:budget:edit,project:project:manage')")
|
||||||
|
@Log(title = "项目预算", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/budget")
|
||||||
|
public AjaxResult saveBudget(@RequestBody ProjectBudget budget)
|
||||||
|
{
|
||||||
|
return toAjax(projectBudgetService.saveProjectBudget(budget));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:budget:list,project:project:manage')")
|
||||||
|
@GetMapping("/ledger/list")
|
||||||
|
public TableDataInfo ledgerList(ProjectFundLedger ledger)
|
||||||
|
{
|
||||||
|
startPage();
|
||||||
|
return getDataTable(projectBudgetService.selectFundLedgerList(ledger));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:budget:list,project:budget:edit,project:category:edit,warehouse:purchase:list,warehouse:purchase:add,warehouse:purchase:edit,warehouse:purchase:import,warehouse:purchase:manage,warehouse:in:list,warehouse:in:add,warehouse:in:edit')")
|
||||||
|
@GetMapping("/category/options")
|
||||||
|
public AjaxResult categoryOptions()
|
||||||
|
{
|
||||||
|
ExpenseCategory query = new ExpenseCategory();
|
||||||
|
query.setStatus(0);
|
||||||
|
return success(projectBudgetService.selectExpenseCategoryList(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('project:category:edit,project:project:manage')")
|
||||||
|
@GetMapping("/category/list")
|
||||||
|
public AjaxResult categoryList(ExpenseCategory category)
|
||||||
|
{
|
||||||
|
List<ExpenseCategory> list = projectBudgetService.selectExpenseCategoryList(category);
|
||||||
|
return success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:category:edit')")
|
||||||
|
@Log(title = "经费科目", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping("/category")
|
||||||
|
public AjaxResult addCategory(@RequestBody ExpenseCategory category)
|
||||||
|
{
|
||||||
|
return toAjax(projectBudgetService.insertExpenseCategory(category));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:category:edit')")
|
||||||
|
@Log(title = "经费科目", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/category")
|
||||||
|
public AjaxResult editCategory(@RequestBody ExpenseCategory category)
|
||||||
|
{
|
||||||
|
return toAjax(projectBudgetService.updateExpenseCategory(category));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('project:category:edit')")
|
||||||
|
@Log(title = "经费科目", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/category/{id}")
|
||||||
|
public AjaxResult removeCategory(@PathVariable Long id)
|
||||||
|
{
|
||||||
|
return toAjax(projectBudgetService.deleteExpenseCategory(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
package com.ruoyi.web.controller.warehouse;
|
package com.ruoyi.web.controller.warehouse;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||||
|
import com.ruoyi.common.annotation.RateLimiter;
|
||||||
|
import com.ruoyi.common.enums.LimitType;
|
||||||
import com.ruoyi.warehouse.domain.agent.AgentAnalyzeRequest;
|
import com.ruoyi.warehouse.domain.agent.AgentAnalyzeRequest;
|
||||||
import com.ruoyi.warehouse.domain.agent.AgentExecuteRequest;
|
import com.ruoyi.warehouse.domain.agent.AgentExecuteRequest;
|
||||||
import com.ruoyi.warehouse.service.IAgentService;
|
import com.ruoyi.warehouse.service.IAgentService;
|
||||||
@ -19,12 +23,16 @@ public class AgentController extends BaseController
|
|||||||
private IAgentService agentService;
|
private IAgentService agentService;
|
||||||
|
|
||||||
@PostMapping("/analyze")
|
@PostMapping("/analyze")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ai:assistant:use')")
|
||||||
|
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
||||||
public AjaxResult analyze(@RequestBody AgentAnalyzeRequest request)
|
public AjaxResult analyze(@RequestBody AgentAnalyzeRequest request)
|
||||||
{
|
{
|
||||||
return success(agentService.analyze(request.getText(), request.getContextResults(), getUsername()));
|
return success(agentService.analyze(request.getText(), request.getContextResults(), getUsername()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/execute")
|
@PostMapping("/execute")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ai:assistant:use')")
|
||||||
|
@RepeatSubmit(interval = 3000, message = "操作正在执行,请勿重复提交")
|
||||||
public AjaxResult execute(@RequestBody AgentExecuteRequest request)
|
public AjaxResult execute(@RequestBody AgentExecuteRequest request)
|
||||||
{
|
{
|
||||||
return success(agentService.execute(request.getPlan(), getUsername()));
|
return success(agentService.execute(request.getPlan(), getUsername()));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.web.controller.warehouse;
|
package com.ruoyi.web.controller.warehouse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@ -15,10 +16,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.annotation.RateLimiter;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.common.enums.LimitType;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseBatch;
|
import com.ruoyi.warehouse.domain.PurchaseBatch;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseBatchAssignRequest;
|
import com.ruoyi.warehouse.domain.PurchaseBatchAssignRequest;
|
||||||
@ -32,6 +35,10 @@ import com.ruoyi.warehouse.service.IPurchaseBatchService;
|
|||||||
import com.ruoyi.warehouse.service.IProductInfoService;
|
import com.ruoyi.warehouse.service.IProductInfoService;
|
||||||
import com.ruoyi.warehouse.service.IPurchaseRequestService;
|
import com.ruoyi.warehouse.service.IPurchaseRequestService;
|
||||||
import com.ruoyi.warehouse.service.IPurchaseSmartImportService;
|
import com.ruoyi.warehouse.service.IPurchaseSmartImportService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseFileUtils;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/warehouse/purchase")
|
@RequestMapping("/warehouse/purchase")
|
||||||
@ -52,6 +59,12 @@ public class PurchaseRequestController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPurchaseSmartImportService purchaseSmartImportService;
|
private IPurchaseSmartImportService purchaseSmartImportService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectBudgetService projectBudgetService;
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:list,warehouse:purchase:manage')")
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:list,warehouse:purchase:manage')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo list(PurchaseRequest purchaseRequest)
|
public TableDataInfo list(PurchaseRequest purchaseRequest)
|
||||||
@ -74,6 +87,7 @@ public class PurchaseRequestController extends BaseController
|
|||||||
@PostMapping("/importData")
|
@PostMapping("/importData")
|
||||||
public AjaxResult importData(MultipartFile file) throws Exception
|
public AjaxResult importData(MultipartFile file) throws Exception
|
||||||
{
|
{
|
||||||
|
WarehouseFileUtils.validateUpload(file, 10L * 1024 * 1024, Set.of("xls", "xlsx"));
|
||||||
ExcelUtil<PurchaseRequest> util = new ExcelUtil<PurchaseRequest>(PurchaseRequest.class);
|
ExcelUtil<PurchaseRequest> util = new ExcelUtil<PurchaseRequest>(PurchaseRequest.class);
|
||||||
List<PurchaseRequest> requestList = util.importExcel(file.getInputStream());
|
List<PurchaseRequest> requestList = util.importExcel(file.getInputStream());
|
||||||
return success(purchaseRequestService.importPurchaseRequest(requestList, getUsername()));
|
return success(purchaseRequestService.importPurchaseRequest(requestList, getUsername()));
|
||||||
@ -82,6 +96,7 @@ public class PurchaseRequestController extends BaseController
|
|||||||
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:add,warehouse:purchase:manage')")
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:add,warehouse:purchase:manage')")
|
||||||
@Log(title = "采购申请智能导入", businessType = BusinessType.IMPORT)
|
@Log(title = "采购申请智能导入", businessType = BusinessType.IMPORT)
|
||||||
@PostMapping("/smart-import")
|
@PostMapping("/smart-import")
|
||||||
|
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||||
public AjaxResult smartImport(MultipartFile file) throws Exception
|
public AjaxResult smartImport(MultipartFile file) throws Exception
|
||||||
{
|
{
|
||||||
return success(purchaseSmartImportService.analyze(file));
|
return success(purchaseSmartImportService.analyze(file));
|
||||||
@ -91,10 +106,14 @@ public class PurchaseRequestController extends BaseController
|
|||||||
@PostMapping("/importTemplate")
|
@PostMapping("/importTemplate")
|
||||||
public void importTemplate(HttpServletResponse response) throws Exception
|
public void importTemplate(HttpServletResponse response) throws Exception
|
||||||
{
|
{
|
||||||
PurchaseTemplateExcelUtil.write(response, productInfoService.selectProductInfoAll());
|
ExpenseCategory query = new ExpenseCategory();
|
||||||
|
query.setStatus(0);
|
||||||
|
PurchaseTemplateExcelUtil.write(response, productInfoService.selectProductInfoAll(),
|
||||||
|
projectInfoService.selectProjectOptions(null, false),
|
||||||
|
projectBudgetService.selectExpenseCategoryList(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:query,warehouse:purchase:manage')")
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:purchase:list,warehouse:purchase:query,warehouse:purchase:manage')")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public AjaxResult getInfo(@PathVariable Long id)
|
public AjaxResult getInfo(@PathVariable Long id)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -26,17 +26,19 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.common.utils.file.FileUtils;
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
import com.ruoyi.warehouse.domain.ProductInfo;
|
import com.ruoyi.warehouse.domain.ProductInfo;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseRequest;
|
import com.ruoyi.warehouse.domain.PurchaseRequest;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
|
||||||
public final class PurchaseTemplateExcelUtil
|
public final class PurchaseTemplateExcelUtil
|
||||||
{
|
{
|
||||||
private static final String[] HEADERS = {
|
private static final String[] HEADERS = {
|
||||||
"关联物料编码", "物料名称", "品牌", "类别", "参数规格", "采购链接",
|
"项目编码", "经费科目编码", "关联物料编码", "物料名称", "品牌", "类别", "参数规格", "采购链接",
|
||||||
"需求数量", "单位", "期望到货日期", "产品备注", "申请备注"
|
"需求数量", "预计含税单价", "单位", "期望到货日期", "产品备注", "申请备注"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] EXPORT_HEADERS = {
|
private static final String[] EXPORT_HEADERS = {
|
||||||
"关联物料编码", "物料名称", "品牌", "类别", "参数规格", "采购链接",
|
"项目编码", "项目名称", "经费科目编码", "经费科目", "关联物料编码", "物料名称", "品牌", "类别", "参数规格", "采购链接",
|
||||||
"需求数量", "单位", "期望到货日期", "申请人", "采购状态", "采购批次", "进度说明",
|
"需求数量", "预计含税单价", "预计总金额", "单位", "期望到货日期", "申请人", "采购状态", "采购批次", "进度说明",
|
||||||
"产品备注", "申请备注", "已验收数量", "待验收数量"
|
"产品备注", "申请备注", "已验收数量", "待验收数量"
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,19 +46,21 @@ public final class PurchaseTemplateExcelUtil
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void write(HttpServletResponse response, List<ProductInfo> products) throws IOException
|
public static void write(HttpServletResponse response, List<ProductInfo> products,
|
||||||
|
List<ProjectInfo> projects, List<ExpenseCategory> categories) throws IOException
|
||||||
{
|
{
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
response.setCharacterEncoding("utf-8");
|
response.setCharacterEncoding("utf-8");
|
||||||
FileUtils.setAttachmentResponseHeader(response, "采购申请导入模板.xlsx");
|
FileUtils.setAttachmentResponseHeader(response, "采购申请导入模板.xlsx");
|
||||||
|
|
||||||
try (XSSFWorkbook workbook = createTemplateWorkbook(products))
|
try (XSSFWorkbook workbook = createTemplateWorkbook(products, projects, categories))
|
||||||
{
|
{
|
||||||
workbook.write(response.getOutputStream());
|
workbook.write(response.getOutputStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static XSSFWorkbook createTemplateWorkbook(List<ProductInfo> products)
|
static XSSFWorkbook createTemplateWorkbook(List<ProductInfo> products,
|
||||||
|
List<ProjectInfo> projects, List<ExpenseCategory> categories)
|
||||||
{
|
{
|
||||||
XSSFWorkbook workbook = new XSSFWorkbook();
|
XSSFWorkbook workbook = new XSSFWorkbook();
|
||||||
Sheet sheet = workbook.createSheet("采购申请数据");
|
Sheet sheet = workbook.createSheet("采购申请数据");
|
||||||
@ -72,12 +76,14 @@ public final class PurchaseTemplateExcelUtil
|
|||||||
}
|
}
|
||||||
sheet.createFreezePane(0, 1);
|
sheet.createFreezePane(0, 1);
|
||||||
|
|
||||||
addOptionColumn(workbook, sheet, options, 0, "物料编码", values(products, "code"));
|
addOptionColumn(workbook, sheet, options, 0, "项目编码", projectValues(projects));
|
||||||
addOptionColumn(workbook, sheet, options, 1, "物料名称", values(products, "name"));
|
addOptionColumn(workbook, sheet, options, 1, "经费科目编码", categoryValues(categories));
|
||||||
addOptionColumn(workbook, sheet, options, 2, "品牌", values(products, "brand"));
|
addOptionColumn(workbook, sheet, options, 2, "物料编码", values(products, "code"));
|
||||||
addOptionColumn(workbook, sheet, options, 3, "类别", values(products, "category"));
|
addOptionColumn(workbook, sheet, options, 3, "物料名称", values(products, "name"));
|
||||||
addOptionColumn(workbook, sheet, options, 4, "参数规格", values(products, "spec"));
|
addOptionColumn(workbook, sheet, options, 4, "品牌", values(products, "brand"));
|
||||||
addOptionColumn(workbook, sheet, options, 7, "单位", values(products, "unit"));
|
addOptionColumn(workbook, sheet, options, 5, "类别", values(products, "category"));
|
||||||
|
addOptionColumn(workbook, sheet, options, 6, "参数规格", values(products, "spec"));
|
||||||
|
addOptionColumn(workbook, sheet, options, 10, "单位", values(products, "unit"));
|
||||||
workbook.setSheetVisibility(workbook.getSheetIndex(options), SheetVisibility.HIDDEN);
|
workbook.setSheetVisibility(workbook.getSheetIndex(options), SheetVisibility.HIDDEN);
|
||||||
return workbook;
|
return workbook;
|
||||||
}
|
}
|
||||||
@ -110,9 +116,10 @@ public final class PurchaseTemplateExcelUtil
|
|||||||
PurchaseRequest request = requests.get(rowIndex);
|
PurchaseRequest request = requests.get(rowIndex);
|
||||||
Row row = sheet.createRow(rowIndex + 1);
|
Row row = sheet.createRow(rowIndex + 1);
|
||||||
Object[] values = {
|
Object[] values = {
|
||||||
request.getProductCode(), request.getMaterialName(), request.getMaterialBrand(),
|
request.getProjectCode(), request.getProjectName(), request.getExpenseCategoryCode(),
|
||||||
|
request.getExpenseCategoryName(), request.getProductCode(), request.getMaterialName(), request.getMaterialBrand(),
|
||||||
request.getCategory(), request.getMaterialParams(),
|
request.getCategory(), request.getMaterialParams(),
|
||||||
request.getPurchaseLink(), request.getQuantity(), request.getUnit(),
|
request.getPurchaseLink(), request.getQuantity(), request.getEstimatedUnitPrice(), request.getEstimatedAmount(), request.getUnit(),
|
||||||
request.getExpectedDate() == null ? null : dateFormat.format(request.getExpectedDate()),
|
request.getExpectedDate() == null ? null : dateFormat.format(request.getExpectedDate()),
|
||||||
request.getApplicantName(), purchaseStatusLabel(request.getPurchaseStatus()),
|
request.getApplicantName(), purchaseStatusLabel(request.getPurchaseStatus()),
|
||||||
request.getPurchaseBatchNo(), request.getProgressRemark(), request.getProductRemark(),
|
request.getPurchaseBatchNo(), request.getProgressRemark(), request.getProductRemark(),
|
||||||
@ -124,12 +131,12 @@ public final class PurchaseTemplateExcelUtil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addOptionColumn(workbook, sheet, options, 0, "物料编码", values(products, "code"));
|
addOptionColumn(workbook, sheet, options, 4, "物料编码", values(products, "code"));
|
||||||
addOptionColumn(workbook, sheet, options, 1, "物料名称", values(products, "name"));
|
addOptionColumn(workbook, sheet, options, 5, "物料名称", values(products, "name"));
|
||||||
addOptionColumn(workbook, sheet, options, 2, "品牌", values(products, "brand"));
|
addOptionColumn(workbook, sheet, options, 6, "品牌", values(products, "brand"));
|
||||||
addOptionColumn(workbook, sheet, options, 3, "类别", values(products, "category"));
|
addOptionColumn(workbook, sheet, options, 7, "类别", values(products, "category"));
|
||||||
addOptionColumn(workbook, sheet, options, 4, "参数规格", values(products, "spec"));
|
addOptionColumn(workbook, sheet, options, 8, "参数规格", values(products, "spec"));
|
||||||
addOptionColumn(workbook, sheet, options, 7, "单位", values(products, "unit"));
|
addOptionColumn(workbook, sheet, options, 13, "单位", values(products, "unit"));
|
||||||
workbook.setSheetVisibility(workbook.getSheetIndex(options), SheetVisibility.HIDDEN);
|
workbook.setSheetVisibility(workbook.getSheetIndex(options), SheetVisibility.HIDDEN);
|
||||||
workbook.write(response.getOutputStream());
|
workbook.write(response.getOutputStream());
|
||||||
}
|
}
|
||||||
@ -203,6 +210,26 @@ public final class PurchaseTemplateExcelUtil
|
|||||||
return new ArrayList<>(values);
|
return new ArrayList<>(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> projectValues(List<ProjectInfo> projects)
|
||||||
|
{
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (ProjectInfo project : projects)
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotBlank(project.getProjectCode())) values.add(project.getProjectCode());
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> categoryValues(List<ExpenseCategory> categories)
|
||||||
|
{
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (ExpenseCategory category : categories)
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotBlank(category.getCategoryCode())) values.add(category.getCategoryCode());
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
private static CellStyle createHeaderStyle(XSSFWorkbook workbook)
|
private static CellStyle createHeaderStyle(XSSFWorkbook workbook)
|
||||||
{
|
{
|
||||||
CellStyle style = workbook.createCellStyle();
|
CellStyle style = workbook.createCellStyle();
|
||||||
|
|||||||
@ -23,7 +23,9 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.warehouse.domain.OutRecord;
|
import com.ruoyi.warehouse.domain.OutRecord;
|
||||||
import com.ruoyi.warehouse.domain.StockInfo;
|
import com.ruoyi.warehouse.domain.StockInfo;
|
||||||
|
import com.ruoyi.warehouse.domain.StockLot;
|
||||||
import com.ruoyi.warehouse.service.IStockInfoService;
|
import com.ruoyi.warehouse.service.IStockInfoService;
|
||||||
|
import com.ruoyi.warehouse.service.IStockLotService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/warehouse/stock")
|
@RequestMapping("/warehouse/stock")
|
||||||
@ -32,6 +34,9 @@ public class StockInfoController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IStockInfoService stockInfoService;
|
private IStockInfoService stockInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IStockLotService stockLotService;
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasPermi('warehouse:stock:list')")
|
@PreAuthorize("@ss.hasPermi('warehouse:stock:list')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo list(StockInfo stockInfo)
|
public TableDataInfo list(StockInfo stockInfo)
|
||||||
@ -71,6 +76,13 @@ public class StockInfoController extends BaseController
|
|||||||
util.exportExcel(response, list, "库存数据");
|
util.exportExcel(response, list, "库存数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('warehouse:stock:query')")
|
||||||
|
@GetMapping("/lots")
|
||||||
|
public AjaxResult lots(StockLot lot)
|
||||||
|
{
|
||||||
|
return success(stockLotService.selectStockLotList(lot));
|
||||||
|
}
|
||||||
|
|
||||||
@PreAuthorize("@ss.hasPermi('warehouse:stock:query')")
|
@PreAuthorize("@ss.hasPermi('warehouse:stock:query')")
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public AjaxResult getInfo(@PathVariable Long id)
|
public AjaxResult getInfo(@PathVariable Long id)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.ruoyi.web.controller.warehouse;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -19,11 +20,18 @@ public class WarehouseUserController extends BaseController
|
|||||||
private ISysUserService userService;
|
private ISysUserService userService;
|
||||||
|
|
||||||
@GetMapping("/optionselect")
|
@GetMapping("/optionselect")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:list,warehouse:in:add,warehouse:in:edit,warehouse:out:list,warehouse:out:add,warehouse:stock:list,warehouse:stock:out,warehouse:purchase:list,warehouse:purchase:manage')")
|
||||||
public AjaxResult optionselect()
|
public AjaxResult optionselect()
|
||||||
{
|
{
|
||||||
SysUser user = new SysUser();
|
SysUser user = new SysUser();
|
||||||
user.setStatus(UserConstants.NORMAL);
|
user.setStatus(UserConstants.NORMAL);
|
||||||
List<SysUser> users = userService.selectUserList(user);
|
List<WarehouseUserOption> users = userService.selectUserList(user).stream()
|
||||||
|
.map(item -> new WarehouseUserOption(item.getUserId(), item.getUserName(), item.getNickName()))
|
||||||
|
.toList();
|
||||||
return success(users);
|
return success(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record WarehouseUserOption(Long userId, String userName, String nickName)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.web.controller.warehouse;
|
package com.ruoyi.web.controller.warehouse;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
@ -11,10 +12,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||||
|
import com.ruoyi.common.annotation.RateLimiter;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.common.enums.LimitType;
|
||||||
import com.ruoyi.warehouse.domain.WmsBatchActionUpdateRequest;
|
import com.ruoyi.warehouse.domain.WmsBatchActionUpdateRequest;
|
||||||
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
||||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||||
@ -28,6 +32,7 @@ public class WmsBatchImportController extends BaseController
|
|||||||
private IWmsBatchImportService batchImportService;
|
private IWmsBatchImportService batchImportService;
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:stock:list,warehouse:in:list,warehouse:out:list,warehouse:check:list,warehouse:product:list,warehouse:area:list')")
|
||||||
public TableDataInfo list(WmsBatchJob job)
|
public TableDataInfo list(WmsBatchJob job)
|
||||||
{
|
{
|
||||||
startPage();
|
startPage();
|
||||||
@ -35,6 +40,8 @@ public class WmsBatchImportController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:add,warehouse:stock:out,warehouse:out:add,warehouse:check:add,warehouse:product:add,warehouse:area:add')")
|
||||||
|
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||||
@Log(title = "WMS批量导入", businessType = BusinessType.IMPORT)
|
@Log(title = "WMS批量导入", businessType = BusinessType.IMPORT)
|
||||||
public AjaxResult upload(MultipartFile file) throws Exception
|
public AjaxResult upload(MultipartFile file) throws Exception
|
||||||
{
|
{
|
||||||
@ -42,36 +49,43 @@ public class WmsBatchImportController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:stock:list,warehouse:in:list,warehouse:out:list,warehouse:check:list,warehouse:product:list,warehouse:area:list')")
|
||||||
public AjaxResult getInfo(@PathVariable Long id)
|
public AjaxResult getInfo(@PathVariable Long id)
|
||||||
{
|
{
|
||||||
return success(batchImportService.selectJobById(id));
|
return success(batchImportService.selectJobById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{jobId}/items")
|
@GetMapping("/{jobId}/items")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:stock:list,warehouse:in:list,warehouse:out:list,warehouse:check:list,warehouse:product:list,warehouse:area:list')")
|
||||||
public AjaxResult items(@PathVariable Long jobId)
|
public AjaxResult items(@PathVariable Long jobId)
|
||||||
{
|
{
|
||||||
return success(batchImportService.selectItemList(jobId));
|
return success(batchImportService.selectItemList(jobId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/item")
|
@PutMapping("/item")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:add,warehouse:in:edit,warehouse:stock:out,warehouse:out:add,warehouse:check:add,warehouse:check:edit,warehouse:product:add,warehouse:area:add')")
|
||||||
public AjaxResult updateItem(@RequestBody WmsBatchItem item)
|
public AjaxResult updateItem(@RequestBody WmsBatchItem item)
|
||||||
{
|
{
|
||||||
return success(batchImportService.updateItem(item));
|
return success(batchImportService.updateItem(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{jobId}/items/action")
|
@PutMapping("/{jobId}/items/action")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:add,warehouse:in:edit,warehouse:stock:out,warehouse:out:add,warehouse:check:add,warehouse:check:edit,warehouse:product:add,warehouse:area:add')")
|
||||||
public AjaxResult updateItemAction(@PathVariable Long jobId, @RequestBody WmsBatchActionUpdateRequest request)
|
public AjaxResult updateItemAction(@PathVariable Long jobId, @RequestBody WmsBatchActionUpdateRequest request)
|
||||||
{
|
{
|
||||||
return toAjax(batchImportService.updateItemAction(jobId, request.getActionType(), request.getItemIds()));
|
return toAjax(batchImportService.updateItemAction(jobId, request.getActionType(), request.getItemIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/item/{id}")
|
@DeleteMapping("/item/{id}")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:add,warehouse:in:edit,warehouse:stock:out,warehouse:out:add,warehouse:check:add,warehouse:check:edit,warehouse:product:add,warehouse:area:add')")
|
||||||
public AjaxResult deleteItem(@PathVariable Long id)
|
public AjaxResult deleteItem(@PathVariable Long id)
|
||||||
{
|
{
|
||||||
return toAjax(batchImportService.deleteItem(id));
|
return toAjax(batchImportService.deleteItem(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{jobId}/confirm")
|
@PostMapping("/{jobId}/confirm")
|
||||||
|
@PreAuthorize("@ss.hasAnyPermi('warehouse:in:add,warehouse:stock:out,warehouse:out:add,warehouse:check:add,warehouse:product:add,warehouse:area:add')")
|
||||||
|
@RepeatSubmit(interval = 5000, message = "批量任务正在执行,请勿重复提交")
|
||||||
@Log(title = "WMS批量确认执行", businessType = BusinessType.INSERT)
|
@Log(title = "WMS批量确认执行", businessType = BusinessType.INSERT)
|
||||||
public AjaxResult confirm(@PathVariable Long jobId)
|
public AjaxResult confirm(@PathVariable Long jobId)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.project.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
public class ExpenseCategory extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String categoryCode;
|
||||||
|
private String categoryName;
|
||||||
|
private Integer sortOrder;
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getCategoryCode() { return categoryCode; }
|
||||||
|
public void setCategoryCode(String categoryCode) { this.categoryCode = categoryCode; }
|
||||||
|
public String getCategoryName() { return categoryName; }
|
||||||
|
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
|
||||||
|
public Integer getSortOrder() { return sortOrder; }
|
||||||
|
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
||||||
|
public Integer getStatus() { return status; }
|
||||||
|
public void setStatus(Integer status) { this.status = status; }
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package com.ruoyi.project.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
public class ProjectBudget extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long projectId;
|
||||||
|
private String projectCode;
|
||||||
|
private String projectName;
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
private String categoryCode;
|
||||||
|
private String categoryName;
|
||||||
|
private BigDecimal budgetAmount;
|
||||||
|
private BigDecimal committedAmount;
|
||||||
|
private BigDecimal spentAmount;
|
||||||
|
private BigDecimal availableAmount;
|
||||||
|
private BigDecimal alertPercent;
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public Long getProjectId() { return projectId; }
|
||||||
|
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||||
|
public String getProjectCode() { return projectCode; }
|
||||||
|
public void setProjectCode(String projectCode) { this.projectCode = projectCode; }
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getCategoryCode() { return categoryCode; }
|
||||||
|
public void setCategoryCode(String categoryCode) { this.categoryCode = categoryCode; }
|
||||||
|
public String getCategoryName() { return categoryName; }
|
||||||
|
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
|
||||||
|
public BigDecimal getBudgetAmount() { return budgetAmount; }
|
||||||
|
public void setBudgetAmount(BigDecimal budgetAmount) { this.budgetAmount = budgetAmount; }
|
||||||
|
public BigDecimal getCommittedAmount() { return committedAmount; }
|
||||||
|
public void setCommittedAmount(BigDecimal committedAmount) { this.committedAmount = committedAmount; }
|
||||||
|
public BigDecimal getSpentAmount() { return spentAmount; }
|
||||||
|
public void setSpentAmount(BigDecimal spentAmount) { this.spentAmount = spentAmount; }
|
||||||
|
public BigDecimal getAvailableAmount() { return availableAmount; }
|
||||||
|
public void setAvailableAmount(BigDecimal availableAmount) { this.availableAmount = availableAmount; }
|
||||||
|
public BigDecimal getAlertPercent() { return alertPercent; }
|
||||||
|
public void setAlertPercent(BigDecimal alertPercent) { this.alertPercent = alertPercent; }
|
||||||
|
public Integer getVersion() { return version; }
|
||||||
|
public void setVersion(Integer version) { this.version = version; }
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package com.ruoyi.project.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
public class ProjectFundLedger extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String ledgerNo;
|
||||||
|
private Long projectId;
|
||||||
|
private String projectName;
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
private String categoryName;
|
||||||
|
private String bizType;
|
||||||
|
private Long bizId;
|
||||||
|
private String bizNo;
|
||||||
|
private BigDecimal budgetDelta;
|
||||||
|
private BigDecimal committedDelta;
|
||||||
|
private BigDecimal spentDelta;
|
||||||
|
private BigDecimal balanceBudget;
|
||||||
|
private BigDecimal balanceCommitted;
|
||||||
|
private BigDecimal balanceSpent;
|
||||||
|
private String idempotencyKey;
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getLedgerNo() { return ledgerNo; }
|
||||||
|
public void setLedgerNo(String ledgerNo) { this.ledgerNo = ledgerNo; }
|
||||||
|
public Long getProjectId() { return projectId; }
|
||||||
|
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getCategoryName() { return categoryName; }
|
||||||
|
public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
|
||||||
|
public String getBizType() { return bizType; }
|
||||||
|
public void setBizType(String bizType) { this.bizType = bizType; }
|
||||||
|
public Long getBizId() { return bizId; }
|
||||||
|
public void setBizId(Long bizId) { this.bizId = bizId; }
|
||||||
|
public String getBizNo() { return bizNo; }
|
||||||
|
public void setBizNo(String bizNo) { this.bizNo = bizNo; }
|
||||||
|
public BigDecimal getBudgetDelta() { return budgetDelta; }
|
||||||
|
public void setBudgetDelta(BigDecimal budgetDelta) { this.budgetDelta = budgetDelta; }
|
||||||
|
public BigDecimal getCommittedDelta() { return committedDelta; }
|
||||||
|
public void setCommittedDelta(BigDecimal committedDelta) { this.committedDelta = committedDelta; }
|
||||||
|
public BigDecimal getSpentDelta() { return spentDelta; }
|
||||||
|
public void setSpentDelta(BigDecimal spentDelta) { this.spentDelta = spentDelta; }
|
||||||
|
public BigDecimal getBalanceBudget() { return balanceBudget; }
|
||||||
|
public void setBalanceBudget(BigDecimal balanceBudget) { this.balanceBudget = balanceBudget; }
|
||||||
|
public BigDecimal getBalanceCommitted() { return balanceCommitted; }
|
||||||
|
public void setBalanceCommitted(BigDecimal balanceCommitted) { this.balanceCommitted = balanceCommitted; }
|
||||||
|
public BigDecimal getBalanceSpent() { return balanceSpent; }
|
||||||
|
public void setBalanceSpent(BigDecimal balanceSpent) { this.balanceSpent = balanceSpent; }
|
||||||
|
public String getIdempotencyKey() { return idempotencyKey; }
|
||||||
|
public void setIdempotencyKey(String idempotencyKey) { this.idempotencyKey = idempotencyKey; }
|
||||||
|
public String getOperator() { return operator; }
|
||||||
|
public void setOperator(String operator) { this.operator = operator; }
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.ruoyi.project.domain;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
public class ProjectInfo extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String projectCode;
|
||||||
|
private String projectName;
|
||||||
|
private Long managerId;
|
||||||
|
private String managerName;
|
||||||
|
private Long deptId;
|
||||||
|
private String deptName;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private Date startDate;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private Date endDate;
|
||||||
|
private Integer projectStatus;
|
||||||
|
private String currency;
|
||||||
|
private List<Long> memberIds;
|
||||||
|
private java.math.BigDecimal budgetAmount;
|
||||||
|
private java.math.BigDecimal committedAmount;
|
||||||
|
private java.math.BigDecimal spentAmount;
|
||||||
|
private java.math.BigDecimal availableAmount;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getProjectCode() { return projectCode; }
|
||||||
|
public void setProjectCode(String projectCode) { this.projectCode = projectCode; }
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public Long getManagerId() { return managerId; }
|
||||||
|
public void setManagerId(Long managerId) { this.managerId = managerId; }
|
||||||
|
public String getManagerName() { return managerName; }
|
||||||
|
public void setManagerName(String managerName) { this.managerName = managerName; }
|
||||||
|
public Long getDeptId() { return deptId; }
|
||||||
|
public void setDeptId(Long deptId) { this.deptId = deptId; }
|
||||||
|
public String getDeptName() { return deptName; }
|
||||||
|
public void setDeptName(String deptName) { this.deptName = deptName; }
|
||||||
|
public Date getStartDate() { return startDate; }
|
||||||
|
public void setStartDate(Date startDate) { this.startDate = startDate; }
|
||||||
|
public Date getEndDate() { return endDate; }
|
||||||
|
public void setEndDate(Date endDate) { this.endDate = endDate; }
|
||||||
|
public Integer getProjectStatus() { return projectStatus; }
|
||||||
|
public void setProjectStatus(Integer projectStatus) { this.projectStatus = projectStatus; }
|
||||||
|
public String getCurrency() { return currency; }
|
||||||
|
public void setCurrency(String currency) { this.currency = currency; }
|
||||||
|
public List<Long> getMemberIds() { return memberIds; }
|
||||||
|
public void setMemberIds(List<Long> memberIds) { this.memberIds = memberIds; }
|
||||||
|
public java.math.BigDecimal getBudgetAmount() { return budgetAmount; }
|
||||||
|
public void setBudgetAmount(java.math.BigDecimal budgetAmount) { this.budgetAmount = budgetAmount; }
|
||||||
|
public java.math.BigDecimal getCommittedAmount() { return committedAmount; }
|
||||||
|
public void setCommittedAmount(java.math.BigDecimal committedAmount) { this.committedAmount = committedAmount; }
|
||||||
|
public java.math.BigDecimal getSpentAmount() { return spentAmount; }
|
||||||
|
public void setSpentAmount(java.math.BigDecimal spentAmount) { this.spentAmount = spentAmount; }
|
||||||
|
public java.math.BigDecimal getAvailableAmount() { return availableAmount; }
|
||||||
|
public void setAvailableAmount(java.math.BigDecimal availableAmount) { this.availableAmount = availableAmount; }
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.ruoyi.project.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectBudget;
|
||||||
|
import com.ruoyi.project.domain.ProjectFundLedger;
|
||||||
|
|
||||||
|
public interface ProjectBudgetMapper
|
||||||
|
{
|
||||||
|
List<ExpenseCategory> selectExpenseCategoryList(ExpenseCategory category);
|
||||||
|
|
||||||
|
ExpenseCategory selectExpenseCategoryById(Long id);
|
||||||
|
|
||||||
|
ExpenseCategory selectExpenseCategoryByCode(String categoryCode);
|
||||||
|
|
||||||
|
int insertExpenseCategory(ExpenseCategory category);
|
||||||
|
|
||||||
|
int updateExpenseCategory(ExpenseCategory category);
|
||||||
|
|
||||||
|
int deleteExpenseCategory(Long id);
|
||||||
|
|
||||||
|
int countCategoryBusiness(Long id);
|
||||||
|
|
||||||
|
List<ProjectBudget> selectProjectBudgetList(@Param("projectId") Long projectId,
|
||||||
|
@Param("userId") Long userId);
|
||||||
|
|
||||||
|
ProjectBudget selectProjectBudgetById(Long id);
|
||||||
|
|
||||||
|
ProjectBudget selectProjectBudgetForUpdate(@Param("projectId") Long projectId,
|
||||||
|
@Param("expenseCategoryId") Long expenseCategoryId);
|
||||||
|
|
||||||
|
int insertProjectBudget(ProjectBudget budget);
|
||||||
|
|
||||||
|
int updateProjectBudgetBalance(ProjectBudget budget);
|
||||||
|
|
||||||
|
List<ProjectFundLedger> selectFundLedgerList(@Param("ledger") ProjectFundLedger ledger,
|
||||||
|
@Param("userId") Long userId);
|
||||||
|
|
||||||
|
ProjectFundLedger selectLedgerByIdempotencyKey(String idempotencyKey);
|
||||||
|
|
||||||
|
int insertFundLedger(ProjectFundLedger ledger);
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.project.mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
|
||||||
|
public interface ProjectInfoMapper
|
||||||
|
{
|
||||||
|
List<ProjectInfo> selectProjectInfoList(@Param("project") ProjectInfo project, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
ProjectInfo selectProjectInfoById(@Param("id") Long id, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
ProjectInfo selectProjectInfoByCode(String projectCode);
|
||||||
|
|
||||||
|
List<ProjectInfo> selectProjectOptions(@Param("keyword") String keyword, @Param("userId") Long userId,
|
||||||
|
@Param("includeArchived") boolean includeArchived);
|
||||||
|
|
||||||
|
int insertProjectInfo(ProjectInfo project);
|
||||||
|
|
||||||
|
int updateProjectInfo(ProjectInfo project);
|
||||||
|
|
||||||
|
int deleteProjectInfo(Long id);
|
||||||
|
|
||||||
|
int countProjectBusiness(Long projectId);
|
||||||
|
|
||||||
|
int deleteProjectMembers(Long projectId);
|
||||||
|
|
||||||
|
int insertProjectMember(@Param("projectId") Long projectId, @Param("userId") Long userId,
|
||||||
|
@Param("memberRole") String memberRole, @Param("createBy") String createBy);
|
||||||
|
|
||||||
|
List<Long> selectProjectMemberIds(Long projectId);
|
||||||
|
|
||||||
|
int countProjectAccess(@Param("projectId") Long projectId, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
Long selectLegacyProjectId();
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package com.ruoyi.project.service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectBudget;
|
||||||
|
import com.ruoyi.project.domain.ProjectFundLedger;
|
||||||
|
|
||||||
|
public interface IProjectBudgetService
|
||||||
|
{
|
||||||
|
List<ExpenseCategory> selectExpenseCategoryList(ExpenseCategory category);
|
||||||
|
|
||||||
|
int insertExpenseCategory(ExpenseCategory category);
|
||||||
|
|
||||||
|
int updateExpenseCategory(ExpenseCategory category);
|
||||||
|
|
||||||
|
int deleteExpenseCategory(Long id);
|
||||||
|
|
||||||
|
List<ProjectBudget> selectProjectBudgetList(Long projectId);
|
||||||
|
|
||||||
|
int saveProjectBudget(ProjectBudget budget);
|
||||||
|
|
||||||
|
List<ProjectFundLedger> selectFundLedgerList(ProjectFundLedger ledger);
|
||||||
|
|
||||||
|
void changeCommitment(Long projectId, Long categoryId, BigDecimal delta, String bizType,
|
||||||
|
Long bizId, String bizNo, String idempotencyKey, String remark);
|
||||||
|
|
||||||
|
void settleExpense(Long projectId, Long categoryId, BigDecimal committedRelease, BigDecimal actualAmount,
|
||||||
|
String bizType, Long bizId, String bizNo, String idempotencyKey, String remark);
|
||||||
|
|
||||||
|
void reverseSettlement(Long projectId, Long categoryId, BigDecimal committedRestore, BigDecimal actualAmount,
|
||||||
|
String bizType, Long bizId, String bizNo, String idempotencyKey, String remark);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.project.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
|
||||||
|
public interface IProjectInfoService
|
||||||
|
{
|
||||||
|
List<ProjectInfo> selectProjectInfoList(ProjectInfo project);
|
||||||
|
|
||||||
|
ProjectInfo selectProjectInfoById(Long id);
|
||||||
|
|
||||||
|
List<ProjectInfo> selectProjectOptions(String keyword, boolean includeArchived);
|
||||||
|
|
||||||
|
int insertProjectInfo(ProjectInfo project);
|
||||||
|
|
||||||
|
int updateProjectInfo(ProjectInfo project);
|
||||||
|
|
||||||
|
int deleteProjectInfo(Long id);
|
||||||
|
|
||||||
|
void requireProjectAccess(Long projectId);
|
||||||
|
|
||||||
|
void requireActiveProject(Long projectId);
|
||||||
|
|
||||||
|
Long selectLegacyProjectId();
|
||||||
|
}
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
package com.ruoyi.project.service.impl;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectBudget;
|
||||||
|
import com.ruoyi.project.domain.ProjectFundLedger;
|
||||||
|
import com.ruoyi.project.mapper.ProjectBudgetMapper;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ProjectBudgetServiceImpl implements IProjectBudgetService
|
||||||
|
{
|
||||||
|
private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProjectBudgetMapper projectBudgetMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExpenseCategory> selectExpenseCategoryList(ExpenseCategory category)
|
||||||
|
{
|
||||||
|
return projectBudgetMapper.selectExpenseCategoryList(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int insertExpenseCategory(ExpenseCategory category)
|
||||||
|
{
|
||||||
|
normalizeCategory(category);
|
||||||
|
if (projectBudgetMapper.selectExpenseCategoryByCode(category.getCategoryCode()) != null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目编码已存在");
|
||||||
|
}
|
||||||
|
category.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
return projectBudgetMapper.insertExpenseCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updateExpenseCategory(ExpenseCategory category)
|
||||||
|
{
|
||||||
|
if (category.getId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目ID不能为空");
|
||||||
|
}
|
||||||
|
ExpenseCategory existing = projectBudgetMapper.selectExpenseCategoryById(category.getId());
|
||||||
|
if (existing == null || "LEGACY".equals(existing.getCategoryCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目不存在或不能修改");
|
||||||
|
}
|
||||||
|
category.setCategoryCode(existing.getCategoryCode());
|
||||||
|
normalizeCategory(category);
|
||||||
|
category.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
return projectBudgetMapper.updateExpenseCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deleteExpenseCategory(Long id)
|
||||||
|
{
|
||||||
|
ExpenseCategory existing = projectBudgetMapper.selectExpenseCategoryById(id);
|
||||||
|
if (existing == null || "LEGACY".equals(existing.getCategoryCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目不存在或不能删除");
|
||||||
|
}
|
||||||
|
if (projectBudgetMapper.countCategoryBusiness(id) > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目已被项目或采购使用,不能删除,可改为停用");
|
||||||
|
}
|
||||||
|
return projectBudgetMapper.deleteExpenseCategory(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProjectBudget> selectProjectBudgetList(Long projectId)
|
||||||
|
{
|
||||||
|
projectInfoService.requireProjectAccess(projectId);
|
||||||
|
return projectBudgetMapper.selectProjectBudgetList(projectId, currentScopeUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int saveProjectBudget(ProjectBudget budget)
|
||||||
|
{
|
||||||
|
if (budget.getProjectId() == null || budget.getExpenseCategoryId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目和经费科目不能为空");
|
||||||
|
}
|
||||||
|
projectInfoService.requireProjectAccess(budget.getProjectId());
|
||||||
|
if (projectBudgetMapper.selectExpenseCategoryById(budget.getExpenseCategoryId()) == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目不存在");
|
||||||
|
}
|
||||||
|
BigDecimal target = money(budget.getBudgetAmount());
|
||||||
|
if (target.signum() < 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("预算金额不能小于0");
|
||||||
|
}
|
||||||
|
ProjectBudget existing = projectBudgetMapper.selectProjectBudgetForUpdate(
|
||||||
|
budget.getProjectId(), budget.getExpenseCategoryId());
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
budget.setBudgetAmount(target);
|
||||||
|
budget.setCommittedAmount(ZERO);
|
||||||
|
budget.setSpentAmount(ZERO);
|
||||||
|
budget.setAlertPercent(validAlertPercent(budget.getAlertPercent()));
|
||||||
|
budget.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
int rows = projectBudgetMapper.insertProjectBudget(budget);
|
||||||
|
insertLedger(budget, target, ZERO, ZERO, "BUDGET_SET", budget.getId(), null,
|
||||||
|
"BUDGET:" + budget.getId() + ":CREATE", "设置项目预算");
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
if (target.compareTo(money(existing.getCommittedAmount()).add(money(existing.getSpentAmount()))) < 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("预算金额不能小于已占用与已支出之和");
|
||||||
|
}
|
||||||
|
BigDecimal delta = target.subtract(money(existing.getBudgetAmount()));
|
||||||
|
existing.setBudgetAmount(target);
|
||||||
|
existing.setAlertPercent(validAlertPercent(budget.getAlertPercent()));
|
||||||
|
existing.setRemark(budget.getRemark());
|
||||||
|
existing.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
int rows = projectBudgetMapper.updateProjectBudgetBalance(existing);
|
||||||
|
if (delta.signum() != 0)
|
||||||
|
{
|
||||||
|
insertLedger(existing, delta, ZERO, ZERO, "BUDGET_ADJUST", existing.getId(), null,
|
||||||
|
null, StringUtils.defaultIfBlank(budget.getRemark(), "调整项目预算"));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProjectFundLedger> selectFundLedgerList(ProjectFundLedger ledger)
|
||||||
|
{
|
||||||
|
if (ledger.getProjectId() != null)
|
||||||
|
{
|
||||||
|
projectInfoService.requireProjectAccess(ledger.getProjectId());
|
||||||
|
}
|
||||||
|
return projectBudgetMapper.selectFundLedgerList(ledger, currentScopeUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void changeCommitment(Long projectId, Long categoryId, BigDecimal delta, String bizType,
|
||||||
|
Long bizId, String bizNo, String idempotencyKey, String remark)
|
||||||
|
{
|
||||||
|
post(projectId, categoryId, ZERO, money(delta), ZERO, bizType, bizId, bizNo, idempotencyKey, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void settleExpense(Long projectId, Long categoryId, BigDecimal committedRelease, BigDecimal actualAmount,
|
||||||
|
String bizType, Long bizId, String bizNo, String idempotencyKey, String remark)
|
||||||
|
{
|
||||||
|
post(projectId, categoryId, ZERO, money(committedRelease).negate(), money(actualAmount),
|
||||||
|
bizType, bizId, bizNo, idempotencyKey, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void reverseSettlement(Long projectId, Long categoryId, BigDecimal committedRestore, BigDecimal actualAmount,
|
||||||
|
String bizType, Long bizId, String bizNo, String idempotencyKey, String remark)
|
||||||
|
{
|
||||||
|
post(projectId, categoryId, ZERO, money(committedRestore), money(actualAmount).negate(),
|
||||||
|
bizType, bizId, bizNo, idempotencyKey, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void post(Long projectId, Long categoryId, BigDecimal budgetDelta, BigDecimal committedDelta,
|
||||||
|
BigDecimal spentDelta, String bizType, Long bizId, String bizNo, String idempotencyKey, String remark)
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotBlank(idempotencyKey)
|
||||||
|
&& projectBudgetMapper.selectLedgerByIdempotencyKey(idempotencyKey) != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProjectBudget budget = projectBudgetMapper.selectProjectBudgetForUpdate(projectId, categoryId);
|
||||||
|
if (budget == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目尚未配置该经费科目的预算");
|
||||||
|
}
|
||||||
|
BigDecimal newBudget = money(budget.getBudgetAmount()).add(budgetDelta);
|
||||||
|
BigDecimal newCommitted = money(budget.getCommittedAmount()).add(committedDelta);
|
||||||
|
BigDecimal newSpent = money(budget.getSpentAmount()).add(spentDelta);
|
||||||
|
if (newBudget.signum() < 0 || newCommitted.signum() < 0 || newSpent.signum() < 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费流水会导致预算、占用或支出出现负数");
|
||||||
|
}
|
||||||
|
if (newCommitted.add(newSpent).compareTo(newBudget) > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目经费不足,可用金额:"
|
||||||
|
+ money(budget.getBudgetAmount()).subtract(money(budget.getCommittedAmount()))
|
||||||
|
.subtract(money(budget.getSpentAmount())));
|
||||||
|
}
|
||||||
|
budget.setBudgetAmount(newBudget);
|
||||||
|
budget.setCommittedAmount(newCommitted);
|
||||||
|
budget.setSpentAmount(newSpent);
|
||||||
|
budget.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
projectBudgetMapper.updateProjectBudgetBalance(budget);
|
||||||
|
insertLedger(budget, budgetDelta, committedDelta, spentDelta, bizType, bizId, bizNo,
|
||||||
|
idempotencyKey, remark);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertLedger(ProjectBudget budget, BigDecimal budgetDelta, BigDecimal committedDelta,
|
||||||
|
BigDecimal spentDelta, String bizType, Long bizId, String bizNo, String idempotencyKey, String remark)
|
||||||
|
{
|
||||||
|
ProjectFundLedger ledger = new ProjectFundLedger();
|
||||||
|
ledger.setLedgerNo(WarehouseNoUtils.next("FUND"));
|
||||||
|
ledger.setProjectId(budget.getProjectId());
|
||||||
|
ledger.setExpenseCategoryId(budget.getExpenseCategoryId());
|
||||||
|
ledger.setBizType(bizType);
|
||||||
|
ledger.setBizId(bizId);
|
||||||
|
ledger.setBizNo(bizNo);
|
||||||
|
ledger.setBudgetDelta(money(budgetDelta));
|
||||||
|
ledger.setCommittedDelta(money(committedDelta));
|
||||||
|
ledger.setSpentDelta(money(spentDelta));
|
||||||
|
ledger.setBalanceBudget(money(budget.getBudgetAmount()));
|
||||||
|
ledger.setBalanceCommitted(money(budget.getCommittedAmount()));
|
||||||
|
ledger.setBalanceSpent(money(budget.getSpentAmount()));
|
||||||
|
ledger.setIdempotencyKey(idempotencyKey);
|
||||||
|
ledger.setOperator(SecurityUtils.getUsername());
|
||||||
|
ledger.setRemark(remark);
|
||||||
|
projectBudgetMapper.insertFundLedger(ledger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeCategory(ExpenseCategory category)
|
||||||
|
{
|
||||||
|
category.setCategoryCode(StringUtils.trim(category.getCategoryCode()));
|
||||||
|
category.setCategoryName(StringUtils.trim(category.getCategoryName()));
|
||||||
|
if (StringUtils.isBlank(category.getCategoryCode()) || StringUtils.isBlank(category.getCategoryName()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目编码和名称不能为空");
|
||||||
|
}
|
||||||
|
if (!category.getCategoryCode().matches("[A-Za-z0-9_-]{2,64}"))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目编码格式不正确");
|
||||||
|
}
|
||||||
|
if (category.getSortOrder() == null)
|
||||||
|
{
|
||||||
|
category.setSortOrder(0);
|
||||||
|
}
|
||||||
|
if (category.getStatus() == null)
|
||||||
|
{
|
||||||
|
category.setStatus(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal validAlertPercent(BigDecimal value)
|
||||||
|
{
|
||||||
|
BigDecimal result = value == null ? new BigDecimal("80.00") : value;
|
||||||
|
if (result.signum() < 0 || result.compareTo(new BigDecimal("100")) > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("预算预警比例必须在0到100之间");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal money(BigDecimal value)
|
||||||
|
{
|
||||||
|
return value == null ? ZERO : value.setScale(2, java.math.RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long currentScopeUserId()
|
||||||
|
{
|
||||||
|
return SecurityUtils.isAdmin() || SecurityUtils.hasPermi("project:project:manage")
|
||||||
|
? null : SecurityUtils.getUserId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,197 @@
|
|||||||
|
package com.ruoyi.project.service.impl;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
import com.ruoyi.project.mapper.ProjectInfoMapper;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ProjectInfoServiceImpl implements IProjectInfoService
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private ProjectInfoMapper projectInfoMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProjectInfo> selectProjectInfoList(ProjectInfo project)
|
||||||
|
{
|
||||||
|
return projectInfoMapper.selectProjectInfoList(project, currentScopeUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProjectInfo selectProjectInfoById(Long id)
|
||||||
|
{
|
||||||
|
ProjectInfo project = projectInfoMapper.selectProjectInfoById(id, currentScopeUserId());
|
||||||
|
if (project == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目不存在或无权访问");
|
||||||
|
}
|
||||||
|
project.setMemberIds(projectInfoMapper.selectProjectMemberIds(id));
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProjectInfo> selectProjectOptions(String keyword, boolean includeArchived)
|
||||||
|
{
|
||||||
|
return projectInfoMapper.selectProjectOptions(StringUtils.trim(keyword), currentScopeUserId(), includeArchived);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int insertProjectInfo(ProjectInfo project)
|
||||||
|
{
|
||||||
|
normalizeAndValidate(project);
|
||||||
|
if (projectInfoMapper.selectProjectInfoByCode(project.getProjectCode()) != null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目编码已存在");
|
||||||
|
}
|
||||||
|
project.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
int rows = projectInfoMapper.insertProjectInfo(project);
|
||||||
|
replaceMembers(project);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int updateProjectInfo(ProjectInfo project)
|
||||||
|
{
|
||||||
|
if (project.getId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目ID不能为空");
|
||||||
|
}
|
||||||
|
ProjectInfo existing = projectInfoMapper.selectProjectInfoById(project.getId(), null);
|
||||||
|
if (existing == null || "LEGACY".equals(existing.getProjectCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目不存在或历史待归属项目不能修改");
|
||||||
|
}
|
||||||
|
normalizeAndValidate(project);
|
||||||
|
ProjectInfo sameCode = projectInfoMapper.selectProjectInfoByCode(project.getProjectCode());
|
||||||
|
if (sameCode != null && !sameCode.getId().equals(project.getId()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目编码已存在");
|
||||||
|
}
|
||||||
|
project.setUpdateBy(SecurityUtils.getUsername());
|
||||||
|
int rows = projectInfoMapper.updateProjectInfo(project);
|
||||||
|
replaceMembers(project);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int deleteProjectInfo(Long id)
|
||||||
|
{
|
||||||
|
ProjectInfo project = projectInfoMapper.selectProjectInfoById(id, null);
|
||||||
|
if (project == null || "LEGACY".equals(project.getProjectCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目不存在或不能删除");
|
||||||
|
}
|
||||||
|
if (projectInfoMapper.countProjectBusiness(id) > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目已有预算、采购、库存或出库业务,不能删除,可改为已归档");
|
||||||
|
}
|
||||||
|
projectInfoMapper.deleteProjectMembers(id);
|
||||||
|
return projectInfoMapper.deleteProjectInfo(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requireProjectAccess(Long projectId)
|
||||||
|
{
|
||||||
|
if (projectId == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目不能为空");
|
||||||
|
}
|
||||||
|
if (!isManageAll() && projectInfoMapper.countProjectAccess(projectId, SecurityUtils.getUserId()) == 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("无权使用该项目");
|
||||||
|
}
|
||||||
|
if (projectInfoMapper.selectProjectInfoById(projectId, null) == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requireActiveProject(Long projectId)
|
||||||
|
{
|
||||||
|
requireProjectAccess(projectId);
|
||||||
|
ProjectInfo project = projectInfoMapper.selectProjectInfoById(projectId, null);
|
||||||
|
if (project.getProjectStatus() == null || (project.getProjectStatus() != 0 && project.getProjectStatus() != 1))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目已完成或归档,不能新增业务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long selectLegacyProjectId()
|
||||||
|
{
|
||||||
|
return projectInfoMapper.selectLegacyProjectId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replaceMembers(ProjectInfo project)
|
||||||
|
{
|
||||||
|
projectInfoMapper.deleteProjectMembers(project.getId());
|
||||||
|
Set<Long> memberIds = new LinkedHashSet<>();
|
||||||
|
if (project.getMemberIds() != null)
|
||||||
|
{
|
||||||
|
memberIds.addAll(project.getMemberIds());
|
||||||
|
}
|
||||||
|
if (project.getManagerId() != null)
|
||||||
|
{
|
||||||
|
memberIds.add(project.getManagerId());
|
||||||
|
}
|
||||||
|
for (Long userId : memberIds)
|
||||||
|
{
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
projectInfoMapper.insertProjectMember(project.getId(), userId,
|
||||||
|
userId.equals(project.getManagerId()) ? "MANAGER" : "MEMBER", SecurityUtils.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeAndValidate(ProjectInfo project)
|
||||||
|
{
|
||||||
|
project.setProjectCode(StringUtils.trim(project.getProjectCode()));
|
||||||
|
project.setProjectName(StringUtils.trim(project.getProjectName()));
|
||||||
|
if (StringUtils.isBlank(project.getProjectCode()) || StringUtils.isBlank(project.getProjectName()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目编码和项目名称不能为空");
|
||||||
|
}
|
||||||
|
if (!project.getProjectCode().matches("[A-Za-z0-9_-]{2,64}"))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目编码只能包含字母、数字、下划线和中划线");
|
||||||
|
}
|
||||||
|
if (project.getStartDate() != null && project.getEndDate() != null
|
||||||
|
&& project.getStartDate().after(project.getEndDate()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("项目结束日期不能早于开始日期");
|
||||||
|
}
|
||||||
|
if (project.getProjectStatus() == null)
|
||||||
|
{
|
||||||
|
project.setProjectStatus(1);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(project.getCurrency()))
|
||||||
|
{
|
||||||
|
project.setCurrency("CNY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long currentScopeUserId()
|
||||||
|
{
|
||||||
|
return isManageAll() ? null : SecurityUtils.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isManageAll()
|
||||||
|
{
|
||||||
|
return SecurityUtils.isAdmin()
|
||||||
|
|| SecurityUtils.hasPermi("project:project:manage")
|
||||||
|
|| SecurityUtils.hasPermi("warehouse:purchase:manage");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.warehouse.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WarehouseAsyncConfig
|
||||||
|
{
|
||||||
|
@Bean(name = "warehouseTaskExecutor")
|
||||||
|
public ThreadPoolTaskExecutor warehouseTaskExecutor()
|
||||||
|
{
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(2);
|
||||||
|
executor.setMaxPoolSize(4);
|
||||||
|
executor.setQueueCapacity(20);
|
||||||
|
executor.setKeepAliveSeconds(60);
|
||||||
|
executor.setThreadNamePrefix("warehouse-task-");
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
executor.setAwaitTerminationSeconds(30);
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.ruoyi.warehouse.domain;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class AiCommand
|
||||||
|
{
|
||||||
|
private Long id;
|
||||||
|
private Long userId;
|
||||||
|
private String username;
|
||||||
|
private String action;
|
||||||
|
private String requiredPermission;
|
||||||
|
private String requestText;
|
||||||
|
private String planJson;
|
||||||
|
private String resultJson;
|
||||||
|
private String commandStatus;
|
||||||
|
private Date expiresAt;
|
||||||
|
private Date confirmedAt;
|
||||||
|
private Date executedAt;
|
||||||
|
private String errorMessage;
|
||||||
|
private Date createTime;
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public Long getUserId() { return userId; }
|
||||||
|
public void setUserId(Long userId) { this.userId = userId; }
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
public String getAction() { return action; }
|
||||||
|
public void setAction(String action) { this.action = action; }
|
||||||
|
public String getRequiredPermission() { return requiredPermission; }
|
||||||
|
public void setRequiredPermission(String requiredPermission) { this.requiredPermission = requiredPermission; }
|
||||||
|
public String getRequestText() { return requestText; }
|
||||||
|
public void setRequestText(String requestText) { this.requestText = requestText; }
|
||||||
|
public String getPlanJson() { return planJson; }
|
||||||
|
public void setPlanJson(String planJson) { this.planJson = planJson; }
|
||||||
|
public String getResultJson() { return resultJson; }
|
||||||
|
public void setResultJson(String resultJson) { this.resultJson = resultJson; }
|
||||||
|
public String getCommandStatus() { return commandStatus; }
|
||||||
|
public void setCommandStatus(String commandStatus) { this.commandStatus = commandStatus; }
|
||||||
|
public Date getExpiresAt() { return expiresAt; }
|
||||||
|
public void setExpiresAt(Date expiresAt) { this.expiresAt = expiresAt; }
|
||||||
|
public Date getConfirmedAt() { return confirmedAt; }
|
||||||
|
public void setConfirmedAt(Date confirmedAt) { this.confirmedAt = confirmedAt; }
|
||||||
|
public Date getExecutedAt() { return executedAt; }
|
||||||
|
public void setExecutedAt(Date executedAt) { this.executedAt = executedAt; }
|
||||||
|
public String getErrorMessage() { return errorMessage; }
|
||||||
|
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
|
||||||
|
public Date getCreateTime() { return createTime; }
|
||||||
|
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||||
|
public Date getUpdateTime() { return updateTime; }
|
||||||
|
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
|
||||||
|
}
|
||||||
@ -26,6 +26,16 @@ public class InRecord extends BaseEntity
|
|||||||
|
|
||||||
private Long arrivalId;
|
private Long arrivalId;
|
||||||
|
|
||||||
|
private Long sourceProjectId;
|
||||||
|
|
||||||
|
@Excel(name = "来源项目")
|
||||||
|
private String sourceProjectName;
|
||||||
|
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
|
||||||
|
@Excel(name = "经费科目")
|
||||||
|
private String expenseCategoryName;
|
||||||
|
|
||||||
@Excel(name = "来源单号")
|
@Excel(name = "来源单号")
|
||||||
private String sourceNo;
|
private String sourceNo;
|
||||||
|
|
||||||
@ -41,9 +51,11 @@ public class InRecord extends BaseEntity
|
|||||||
@Excel(name = "系统操作人")
|
@Excel(name = "系统操作人")
|
||||||
private String operator;
|
private String operator;
|
||||||
|
|
||||||
@Excel(name = "入库人")
|
|
||||||
private String inOperator;
|
private String inOperator;
|
||||||
|
|
||||||
|
@Excel(name = "入库人")
|
||||||
|
private String inOperatorName;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@Excel(name = "确认入库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "确认入库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date confirmTime;
|
private Date confirmTime;
|
||||||
@ -86,6 +98,14 @@ public class InRecord extends BaseEntity
|
|||||||
public void setPurchaseRequestId(Long purchaseRequestId) { this.purchaseRequestId = purchaseRequestId; }
|
public void setPurchaseRequestId(Long purchaseRequestId) { this.purchaseRequestId = purchaseRequestId; }
|
||||||
public Long getArrivalId() { return arrivalId; }
|
public Long getArrivalId() { return arrivalId; }
|
||||||
public void setArrivalId(Long arrivalId) { this.arrivalId = arrivalId; }
|
public void setArrivalId(Long arrivalId) { this.arrivalId = arrivalId; }
|
||||||
|
public Long getSourceProjectId() { return sourceProjectId; }
|
||||||
|
public void setSourceProjectId(Long sourceProjectId) { this.sourceProjectId = sourceProjectId; }
|
||||||
|
public String getSourceProjectName() { return sourceProjectName; }
|
||||||
|
public void setSourceProjectName(String sourceProjectName) { this.sourceProjectName = sourceProjectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
public String getSourceNo() { return sourceNo; }
|
public String getSourceNo() { return sourceNo; }
|
||||||
public void setSourceNo(String sourceNo) { this.sourceNo = sourceNo; }
|
public void setSourceNo(String sourceNo) { this.sourceNo = sourceNo; }
|
||||||
|
|
||||||
@ -103,6 +123,8 @@ public class InRecord extends BaseEntity
|
|||||||
public void setOperator(String operator) { this.operator = operator; }
|
public void setOperator(String operator) { this.operator = operator; }
|
||||||
public String getInOperator() { return inOperator; }
|
public String getInOperator() { return inOperator; }
|
||||||
public void setInOperator(String inOperator) { this.inOperator = inOperator; }
|
public void setInOperator(String inOperator) { this.inOperator = inOperator; }
|
||||||
|
public String getInOperatorName() { return inOperatorName; }
|
||||||
|
public void setInOperatorName(String inOperatorName) { this.inOperatorName = inOperatorName; }
|
||||||
public Date getConfirmTime() { return confirmTime; }
|
public Date getConfirmTime() { return confirmTime; }
|
||||||
public void setConfirmTime(Date confirmTime) { this.confirmTime = confirmTime; }
|
public void setConfirmTime(Date confirmTime) { this.confirmTime = confirmTime; }
|
||||||
public String getAreaCode() { return areaCode; }
|
public String getAreaCode() { return areaCode; }
|
||||||
|
|||||||
@ -20,6 +20,10 @@ public class OutRecord extends BaseEntity
|
|||||||
|
|
||||||
private Long areaId;
|
private Long areaId;
|
||||||
private Long productId;
|
private Long productId;
|
||||||
|
private Long usageProjectId;
|
||||||
|
|
||||||
|
@Excel(name = "使用项目")
|
||||||
|
private String usageProjectName;
|
||||||
|
|
||||||
@Excel(name = "出库类型", readConverterExp = "1=生产出库,2=退货出库,3=销售出库")
|
@Excel(name = "出库类型", readConverterExp = "1=生产出库,2=退货出库,3=销售出库")
|
||||||
private Integer outType;
|
private Integer outType;
|
||||||
@ -30,12 +34,22 @@ public class OutRecord extends BaseEntity
|
|||||||
@Excel(name = "关联单据号")
|
@Excel(name = "关联单据号")
|
||||||
private String sourceNo;
|
private String sourceNo;
|
||||||
|
|
||||||
|
private Integer crossProjectFlag;
|
||||||
|
|
||||||
|
@Excel(name = "跨项目原因")
|
||||||
|
private String crossProjectReason;
|
||||||
|
|
||||||
|
@Excel(name = "来源项目")
|
||||||
|
private String sourceProjectNames;
|
||||||
|
|
||||||
@Excel(name = "系统操作人")
|
@Excel(name = "系统操作人")
|
||||||
private String operator;
|
private String operator;
|
||||||
|
|
||||||
@Excel(name = "出库人")
|
|
||||||
private String outOperator;
|
private String outOperator;
|
||||||
|
|
||||||
|
@Excel(name = "出库人")
|
||||||
|
private String outOperatorName;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@Excel(name = "出库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
@Excel(name = "出库时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date outTime;
|
private Date outTime;
|
||||||
@ -74,6 +88,10 @@ public class OutRecord extends BaseEntity
|
|||||||
@NotNull(message = "产品不能为空")
|
@NotNull(message = "产品不能为空")
|
||||||
public Long getProductId() { return productId; }
|
public Long getProductId() { return productId; }
|
||||||
public void setProductId(Long productId) { this.productId = productId; }
|
public void setProductId(Long productId) { this.productId = productId; }
|
||||||
|
public Long getUsageProjectId() { return usageProjectId; }
|
||||||
|
public void setUsageProjectId(Long usageProjectId) { this.usageProjectId = usageProjectId; }
|
||||||
|
public String getUsageProjectName() { return usageProjectName; }
|
||||||
|
public void setUsageProjectName(String usageProjectName) { this.usageProjectName = usageProjectName; }
|
||||||
|
|
||||||
@NotNull(message = "出库类型不能为空")
|
@NotNull(message = "出库类型不能为空")
|
||||||
public Integer getOutType() { return outType; }
|
public Integer getOutType() { return outType; }
|
||||||
@ -85,10 +103,18 @@ public class OutRecord extends BaseEntity
|
|||||||
public void setOutNum(BigDecimal outNum) { this.outNum = outNum; }
|
public void setOutNum(BigDecimal outNum) { this.outNum = outNum; }
|
||||||
public String getSourceNo() { return sourceNo; }
|
public String getSourceNo() { return sourceNo; }
|
||||||
public void setSourceNo(String sourceNo) { this.sourceNo = sourceNo; }
|
public void setSourceNo(String sourceNo) { this.sourceNo = sourceNo; }
|
||||||
|
public Integer getCrossProjectFlag() { return crossProjectFlag; }
|
||||||
|
public void setCrossProjectFlag(Integer crossProjectFlag) { this.crossProjectFlag = crossProjectFlag; }
|
||||||
|
public String getCrossProjectReason() { return crossProjectReason; }
|
||||||
|
public void setCrossProjectReason(String crossProjectReason) { this.crossProjectReason = crossProjectReason; }
|
||||||
|
public String getSourceProjectNames() { return sourceProjectNames; }
|
||||||
|
public void setSourceProjectNames(String sourceProjectNames) { this.sourceProjectNames = sourceProjectNames; }
|
||||||
public String getOperator() { return operator; }
|
public String getOperator() { return operator; }
|
||||||
public void setOperator(String operator) { this.operator = operator; }
|
public void setOperator(String operator) { this.operator = operator; }
|
||||||
public String getOutOperator() { return outOperator; }
|
public String getOutOperator() { return outOperator; }
|
||||||
public void setOutOperator(String outOperator) { this.outOperator = outOperator; }
|
public void setOutOperator(String outOperator) { this.outOperator = outOperator; }
|
||||||
|
public String getOutOperatorName() { return outOperatorName; }
|
||||||
|
public void setOutOperatorName(String outOperatorName) { this.outOperatorName = outOperatorName; }
|
||||||
public Date getOutTime() { return outTime; }
|
public Date getOutTime() { return outTime; }
|
||||||
public void setOutTime(Date outTime) { this.outTime = outTime; }
|
public void setOutTime(Date outTime) { this.outTime = outTime; }
|
||||||
public String getAreaCode() { return areaCode; }
|
public String getAreaCode() { return areaCode; }
|
||||||
|
|||||||
@ -15,6 +15,8 @@ public class PurchaseArrival extends BaseEntity
|
|||||||
private String arrivalNo;
|
private String arrivalNo;
|
||||||
private Long purchaseRequestId;
|
private Long purchaseRequestId;
|
||||||
private BigDecimal arrivalQuantity;
|
private BigDecimal arrivalQuantity;
|
||||||
|
private BigDecimal actualUnitPrice;
|
||||||
|
private BigDecimal actualAmount;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private Date arrivalDate;
|
private Date arrivalDate;
|
||||||
@ -43,6 +45,11 @@ public class PurchaseArrival extends BaseEntity
|
|||||||
private String materialBrand;
|
private String materialBrand;
|
||||||
private String materialParams;
|
private String materialParams;
|
||||||
private String unit;
|
private String unit;
|
||||||
|
private Long projectId;
|
||||||
|
private String projectName;
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
private String expenseCategoryName;
|
||||||
|
private BigDecimal estimatedUnitPrice;
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
public void setId(Long id) { this.id = id; }
|
public void setId(Long id) { this.id = id; }
|
||||||
@ -55,6 +62,10 @@ public class PurchaseArrival extends BaseEntity
|
|||||||
@DecimalMin(value = "0.001", message = "到货数量必须大于0")
|
@DecimalMin(value = "0.001", message = "到货数量必须大于0")
|
||||||
public BigDecimal getArrivalQuantity() { return arrivalQuantity; }
|
public BigDecimal getArrivalQuantity() { return arrivalQuantity; }
|
||||||
public void setArrivalQuantity(BigDecimal arrivalQuantity) { this.arrivalQuantity = arrivalQuantity; }
|
public void setArrivalQuantity(BigDecimal arrivalQuantity) { this.arrivalQuantity = arrivalQuantity; }
|
||||||
|
public BigDecimal getActualUnitPrice() { return actualUnitPrice; }
|
||||||
|
public void setActualUnitPrice(BigDecimal actualUnitPrice) { this.actualUnitPrice = actualUnitPrice; }
|
||||||
|
public BigDecimal getActualAmount() { return actualAmount; }
|
||||||
|
public void setActualAmount(BigDecimal actualAmount) { this.actualAmount = actualAmount; }
|
||||||
|
|
||||||
@NotNull(message = "到货日期不能为空")
|
@NotNull(message = "到货日期不能为空")
|
||||||
public Date getArrivalDate() { return arrivalDate; }
|
public Date getArrivalDate() { return arrivalDate; }
|
||||||
@ -93,4 +104,14 @@ public class PurchaseArrival extends BaseEntity
|
|||||||
public void setMaterialParams(String materialParams) { this.materialParams = materialParams; }
|
public void setMaterialParams(String materialParams) { this.materialParams = materialParams; }
|
||||||
public String getUnit() { return unit; }
|
public String getUnit() { return unit; }
|
||||||
public void setUnit(String unit) { this.unit = unit; }
|
public void setUnit(String unit) { this.unit = unit; }
|
||||||
|
public Long getProjectId() { return projectId; }
|
||||||
|
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
|
public BigDecimal getEstimatedUnitPrice() { return estimatedUnitPrice; }
|
||||||
|
public void setEstimatedUnitPrice(BigDecimal estimatedUnitPrice) { this.estimatedUnitPrice = estimatedUnitPrice; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,22 @@ public class PurchaseRequest extends BaseEntity
|
|||||||
|
|
||||||
private Long purchaseBatchId;
|
private Long purchaseBatchId;
|
||||||
|
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
@Excel(name = "项目编码")
|
||||||
|
private String projectCode;
|
||||||
|
|
||||||
|
@Excel(name = "项目名称", type = Excel.Type.EXPORT)
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
|
||||||
|
@Excel(name = "经费科目编码")
|
||||||
|
private String expenseCategoryCode;
|
||||||
|
|
||||||
|
@Excel(name = "经费科目", type = Excel.Type.EXPORT)
|
||||||
|
private String expenseCategoryName;
|
||||||
|
|
||||||
@Excel(name = "关联物料编码", prompt = "已有物料建议填写准确编码;留空时按名称、品牌、类别、参数规格匹配")
|
@Excel(name = "关联物料编码", prompt = "已有物料建议填写准确编码;留空时按名称、品牌、类别、参数规格匹配")
|
||||||
private String productCode;
|
private String productCode;
|
||||||
|
|
||||||
@ -42,6 +58,12 @@ public class PurchaseRequest extends BaseEntity
|
|||||||
@Excel(name = "需求数量")
|
@Excel(name = "需求数量")
|
||||||
private BigDecimal quantity;
|
private BigDecimal quantity;
|
||||||
|
|
||||||
|
@Excel(name = "预计含税单价")
|
||||||
|
private BigDecimal estimatedUnitPrice;
|
||||||
|
|
||||||
|
@Excel(name = "预计总金额", type = Excel.Type.EXPORT)
|
||||||
|
private BigDecimal estimatedAmount;
|
||||||
|
|
||||||
@Excel(name = "单位")
|
@Excel(name = "单位")
|
||||||
private String unit;
|
private String unit;
|
||||||
|
|
||||||
@ -90,6 +112,18 @@ public class PurchaseRequest extends BaseEntity
|
|||||||
public void setProductId(Long productId) { this.productId = productId; }
|
public void setProductId(Long productId) { this.productId = productId; }
|
||||||
public Long getPurchaseBatchId() { return purchaseBatchId; }
|
public Long getPurchaseBatchId() { return purchaseBatchId; }
|
||||||
public void setPurchaseBatchId(Long purchaseBatchId) { this.purchaseBatchId = purchaseBatchId; }
|
public void setPurchaseBatchId(Long purchaseBatchId) { this.purchaseBatchId = purchaseBatchId; }
|
||||||
|
public Long getProjectId() { return projectId; }
|
||||||
|
public void setProjectId(Long projectId) { this.projectId = projectId; }
|
||||||
|
public String getProjectCode() { return projectCode; }
|
||||||
|
public void setProjectCode(String projectCode) { this.projectCode = projectCode; }
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getExpenseCategoryCode() { return expenseCategoryCode; }
|
||||||
|
public void setExpenseCategoryCode(String expenseCategoryCode) { this.expenseCategoryCode = expenseCategoryCode; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
public String getProductCode() { return productCode; }
|
public String getProductCode() { return productCode; }
|
||||||
public void setProductCode(String productCode) { this.productCode = productCode; }
|
public void setProductCode(String productCode) { this.productCode = productCode; }
|
||||||
|
|
||||||
@ -109,6 +143,10 @@ public class PurchaseRequest extends BaseEntity
|
|||||||
@DecimalMin(value = "0.001", message = "需求数量必须大于0")
|
@DecimalMin(value = "0.001", message = "需求数量必须大于0")
|
||||||
public BigDecimal getQuantity() { return quantity; }
|
public BigDecimal getQuantity() { return quantity; }
|
||||||
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
|
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
|
||||||
|
public BigDecimal getEstimatedUnitPrice() { return estimatedUnitPrice; }
|
||||||
|
public void setEstimatedUnitPrice(BigDecimal estimatedUnitPrice) { this.estimatedUnitPrice = estimatedUnitPrice; }
|
||||||
|
public BigDecimal getEstimatedAmount() { return estimatedAmount; }
|
||||||
|
public void setEstimatedAmount(BigDecimal estimatedAmount) { this.estimatedAmount = estimatedAmount; }
|
||||||
public String getUnit() { return unit; }
|
public String getUnit() { return unit; }
|
||||||
public void setUnit(String unit) { this.unit = unit; }
|
public void setUnit(String unit) { this.unit = unit; }
|
||||||
public Date getExpectedDate() { return expectedDate; }
|
public Date getExpectedDate() { return expectedDate; }
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.ruoyi.warehouse.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
public class StockLot extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String lotNo;
|
||||||
|
private Long areaId;
|
||||||
|
private String areaName;
|
||||||
|
private Long productId;
|
||||||
|
private String productName;
|
||||||
|
private Long sourceProjectId;
|
||||||
|
private String sourceProjectName;
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
private String expenseCategoryName;
|
||||||
|
private Long purchaseRequestId;
|
||||||
|
private String purchaseRequestNo;
|
||||||
|
private Long arrivalId;
|
||||||
|
private Long inRecordId;
|
||||||
|
private BigDecimal receivedQuantity;
|
||||||
|
private BigDecimal availableQuantity;
|
||||||
|
private BigDecimal unitCost;
|
||||||
|
private Integer lotStatus;
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public void setId(Long id) { this.id = id; }
|
||||||
|
public String getLotNo() { return lotNo; }
|
||||||
|
public void setLotNo(String lotNo) { this.lotNo = lotNo; }
|
||||||
|
public Long getAreaId() { return areaId; }
|
||||||
|
public void setAreaId(Long areaId) { this.areaId = areaId; }
|
||||||
|
public String getAreaName() { return areaName; }
|
||||||
|
public void setAreaName(String areaName) { this.areaName = areaName; }
|
||||||
|
public Long getProductId() { return productId; }
|
||||||
|
public void setProductId(Long productId) { this.productId = productId; }
|
||||||
|
public String getProductName() { return productName; }
|
||||||
|
public void setProductName(String productName) { this.productName = productName; }
|
||||||
|
public Long getSourceProjectId() { return sourceProjectId; }
|
||||||
|
public void setSourceProjectId(Long sourceProjectId) { this.sourceProjectId = sourceProjectId; }
|
||||||
|
public String getSourceProjectName() { return sourceProjectName; }
|
||||||
|
public void setSourceProjectName(String sourceProjectName) { this.sourceProjectName = sourceProjectName; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
|
public Long getPurchaseRequestId() { return purchaseRequestId; }
|
||||||
|
public void setPurchaseRequestId(Long purchaseRequestId) { this.purchaseRequestId = purchaseRequestId; }
|
||||||
|
public String getPurchaseRequestNo() { return purchaseRequestNo; }
|
||||||
|
public void setPurchaseRequestNo(String purchaseRequestNo) { this.purchaseRequestNo = purchaseRequestNo; }
|
||||||
|
public Long getArrivalId() { return arrivalId; }
|
||||||
|
public void setArrivalId(Long arrivalId) { this.arrivalId = arrivalId; }
|
||||||
|
public Long getInRecordId() { return inRecordId; }
|
||||||
|
public void setInRecordId(Long inRecordId) { this.inRecordId = inRecordId; }
|
||||||
|
public BigDecimal getReceivedQuantity() { return receivedQuantity; }
|
||||||
|
public void setReceivedQuantity(BigDecimal receivedQuantity) { this.receivedQuantity = receivedQuantity; }
|
||||||
|
public BigDecimal getAvailableQuantity() { return availableQuantity; }
|
||||||
|
public void setAvailableQuantity(BigDecimal availableQuantity) { this.availableQuantity = availableQuantity; }
|
||||||
|
public BigDecimal getUnitCost() { return unitCost; }
|
||||||
|
public void setUnitCost(BigDecimal unitCost) { this.unitCost = unitCost; }
|
||||||
|
public Integer getLotStatus() { return lotStatus; }
|
||||||
|
public void setLotStatus(Integer lotStatus) { this.lotStatus = lotStatus; }
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.warehouse.domain;
|
package com.ruoyi.warehouse.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.ruoyi.common.core.domain.BaseEntity;
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
|
||||||
public class WmsBatchJob extends BaseEntity
|
public class WmsBatchJob extends BaseEntity
|
||||||
@ -69,6 +70,7 @@ public class WmsBatchJob extends BaseEntity
|
|||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
public String getFilePath()
|
public String getFilePath()
|
||||||
{
|
{
|
||||||
return filePath;
|
return filePath;
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import java.util.List;
|
|||||||
|
|
||||||
public class AgentPlan
|
public class AgentPlan
|
||||||
{
|
{
|
||||||
|
private Long commandId;
|
||||||
|
private String expiresAt;
|
||||||
private String action;
|
private String action;
|
||||||
private String summary;
|
private String summary;
|
||||||
private String message;
|
private String message;
|
||||||
@ -15,6 +17,9 @@ public class AgentPlan
|
|||||||
private Long productId;
|
private Long productId;
|
||||||
private Long stockId;
|
private Long stockId;
|
||||||
private Long recordId;
|
private Long recordId;
|
||||||
|
private Long sourceProjectId;
|
||||||
|
private Long expenseCategoryId;
|
||||||
|
private Long usageProjectId;
|
||||||
private Integer referenceIndex;
|
private Integer referenceIndex;
|
||||||
private String areaName;
|
private String areaName;
|
||||||
private String productName;
|
private String productName;
|
||||||
@ -27,6 +32,10 @@ public class AgentPlan
|
|||||||
private Integer outType;
|
private Integer outType;
|
||||||
private String operatorName;
|
private String operatorName;
|
||||||
private String recordNo;
|
private String recordNo;
|
||||||
|
private String sourceProjectName;
|
||||||
|
private String expenseCategoryName;
|
||||||
|
private String usageProjectName;
|
||||||
|
private String crossProjectReason;
|
||||||
private String remark;
|
private String remark;
|
||||||
private BigDecimal currentStock;
|
private BigDecimal currentStock;
|
||||||
private BigDecimal afterStock;
|
private BigDecimal afterStock;
|
||||||
@ -35,6 +44,11 @@ public class AgentPlan
|
|||||||
private List<AgentResultItem> results = new ArrayList<>();
|
private List<AgentResultItem> results = new ArrayList<>();
|
||||||
private AgentPlan nextPlan;
|
private AgentPlan nextPlan;
|
||||||
|
|
||||||
|
public Long getCommandId() { return commandId; }
|
||||||
|
public void setCommandId(Long commandId) { this.commandId = commandId; }
|
||||||
|
public String getExpiresAt() { return expiresAt; }
|
||||||
|
public void setExpiresAt(String expiresAt) { this.expiresAt = expiresAt; }
|
||||||
|
|
||||||
public String getAction()
|
public String getAction()
|
||||||
{
|
{
|
||||||
return action;
|
return action;
|
||||||
@ -125,6 +139,13 @@ public class AgentPlan
|
|||||||
this.recordId = recordId;
|
this.recordId = recordId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getSourceProjectId() { return sourceProjectId; }
|
||||||
|
public void setSourceProjectId(Long sourceProjectId) { this.sourceProjectId = sourceProjectId; }
|
||||||
|
public Long getExpenseCategoryId() { return expenseCategoryId; }
|
||||||
|
public void setExpenseCategoryId(Long expenseCategoryId) { this.expenseCategoryId = expenseCategoryId; }
|
||||||
|
public Long getUsageProjectId() { return usageProjectId; }
|
||||||
|
public void setUsageProjectId(Long usageProjectId) { this.usageProjectId = usageProjectId; }
|
||||||
|
|
||||||
public Integer getReferenceIndex()
|
public Integer getReferenceIndex()
|
||||||
{
|
{
|
||||||
return referenceIndex;
|
return referenceIndex;
|
||||||
@ -245,6 +266,15 @@ public class AgentPlan
|
|||||||
this.recordNo = recordNo;
|
this.recordNo = recordNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSourceProjectName() { return sourceProjectName; }
|
||||||
|
public void setSourceProjectName(String sourceProjectName) { this.sourceProjectName = sourceProjectName; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
|
public String getUsageProjectName() { return usageProjectName; }
|
||||||
|
public void setUsageProjectName(String usageProjectName) { this.usageProjectName = usageProjectName; }
|
||||||
|
public String getCrossProjectReason() { return crossProjectReason; }
|
||||||
|
public void setCrossProjectReason(String crossProjectReason) { this.crossProjectReason = crossProjectReason; }
|
||||||
|
|
||||||
public String getRemark()
|
public String getRemark()
|
||||||
{
|
{
|
||||||
return remark;
|
return remark;
|
||||||
|
|||||||
@ -15,6 +15,15 @@ public class AgentResultItem
|
|||||||
private String unit;
|
private String unit;
|
||||||
private BigDecimal stockNum;
|
private BigDecimal stockNum;
|
||||||
private String recordNo;
|
private String recordNo;
|
||||||
|
private String projectName;
|
||||||
|
private String expenseCategoryName;
|
||||||
|
private Integer purchaseStatus;
|
||||||
|
private String purchaseStatusName;
|
||||||
|
private BigDecimal quantity;
|
||||||
|
private BigDecimal acceptedQuantity;
|
||||||
|
private BigDecimal pendingArrivalQuantity;
|
||||||
|
private BigDecimal estimatedAmount;
|
||||||
|
private String expectedDate;
|
||||||
|
|
||||||
public Long getId()
|
public Long getId()
|
||||||
{
|
{
|
||||||
@ -125,4 +134,23 @@ public class AgentResultItem
|
|||||||
{
|
{
|
||||||
this.recordNo = recordNo;
|
this.recordNo = recordNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProjectName() { return projectName; }
|
||||||
|
public void setProjectName(String projectName) { this.projectName = projectName; }
|
||||||
|
public String getExpenseCategoryName() { return expenseCategoryName; }
|
||||||
|
public void setExpenseCategoryName(String expenseCategoryName) { this.expenseCategoryName = expenseCategoryName; }
|
||||||
|
public Integer getPurchaseStatus() { return purchaseStatus; }
|
||||||
|
public void setPurchaseStatus(Integer purchaseStatus) { this.purchaseStatus = purchaseStatus; }
|
||||||
|
public String getPurchaseStatusName() { return purchaseStatusName; }
|
||||||
|
public void setPurchaseStatusName(String purchaseStatusName) { this.purchaseStatusName = purchaseStatusName; }
|
||||||
|
public BigDecimal getQuantity() { return quantity; }
|
||||||
|
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
|
||||||
|
public BigDecimal getAcceptedQuantity() { return acceptedQuantity; }
|
||||||
|
public void setAcceptedQuantity(BigDecimal acceptedQuantity) { this.acceptedQuantity = acceptedQuantity; }
|
||||||
|
public BigDecimal getPendingArrivalQuantity() { return pendingArrivalQuantity; }
|
||||||
|
public void setPendingArrivalQuantity(BigDecimal pendingArrivalQuantity) { this.pendingArrivalQuantity = pendingArrivalQuantity; }
|
||||||
|
public BigDecimal getEstimatedAmount() { return estimatedAmount; }
|
||||||
|
public void setEstimatedAmount(BigDecimal estimatedAmount) { this.estimatedAmount = estimatedAmount; }
|
||||||
|
public String getExpectedDate() { return expectedDate; }
|
||||||
|
public void setExpectedDate(String expectedDate) { this.expectedDate = expectedDate; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.warehouse.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.ruoyi.warehouse.domain.AiCommand;
|
||||||
|
|
||||||
|
public interface AiCommandMapper
|
||||||
|
{
|
||||||
|
int insertAiCommand(AiCommand command);
|
||||||
|
|
||||||
|
AiCommand selectAiCommandForUpdate(@Param("id") Long id, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
int markExecuting(@Param("id") Long id, @Param("userId") Long userId);
|
||||||
|
|
||||||
|
int markSucceeded(@Param("id") Long id, @Param("resultJson") String resultJson);
|
||||||
|
}
|
||||||
@ -12,4 +12,6 @@ public interface OutRecordMapper
|
|||||||
public OutRecord checkOutNoUnique(String outNo);
|
public OutRecord checkOutNoUnique(String outNo);
|
||||||
|
|
||||||
public int insertOutRecord(OutRecord outRecord);
|
public int insertOutRecord(OutRecord outRecord);
|
||||||
|
|
||||||
|
public int updateOutRecordProjectFlags(OutRecord outRecord);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.warehouse.mapper;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import com.ruoyi.warehouse.domain.StockLot;
|
||||||
|
|
||||||
|
public interface StockLotMapper
|
||||||
|
{
|
||||||
|
List<StockLot> selectStockLotList(StockLot lot);
|
||||||
|
|
||||||
|
List<StockLot> selectAvailableLotsForUpdate(@Param("areaId") Long areaId,
|
||||||
|
@Param("productId") Long productId);
|
||||||
|
|
||||||
|
StockLot selectStockLotByInRecordId(Long inRecordId);
|
||||||
|
|
||||||
|
int insertStockLot(StockLot lot);
|
||||||
|
|
||||||
|
int deductStockLot(@Param("id") Long id, @Param("quantity") BigDecimal quantity);
|
||||||
|
|
||||||
|
int insertOutRecordLot(@Param("outRecordId") Long outRecordId, @Param("stockLotId") Long stockLotId,
|
||||||
|
@Param("sourceProjectId") Long sourceProjectId, @Param("usageProjectId") Long usageProjectId,
|
||||||
|
@Param("quantity") BigDecimal quantity, @Param("unitCost") BigDecimal unitCost,
|
||||||
|
@Param("amount") BigDecimal amount);
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.warehouse.mapper;
|
package com.ruoyi.warehouse.mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import com.ruoyi.warehouse.domain.WarehouseArea;
|
import com.ruoyi.warehouse.domain.WarehouseArea;
|
||||||
|
|
||||||
public interface WarehouseAreaMapper
|
public interface WarehouseAreaMapper
|
||||||
@ -11,6 +12,8 @@ public interface WarehouseAreaMapper
|
|||||||
|
|
||||||
public WarehouseArea selectWarehouseAreaById(Long id);
|
public WarehouseArea selectWarehouseAreaById(Long id);
|
||||||
|
|
||||||
|
public WarehouseArea selectWarehouseAreaByNameOrCode(@Param("value") String value);
|
||||||
|
|
||||||
public WarehouseArea checkAreaCodeUnique(String areaCode);
|
public WarehouseArea checkAreaCodeUnique(String areaCode);
|
||||||
|
|
||||||
public int insertWarehouseArea(WarehouseArea warehouseArea);
|
public int insertWarehouseArea(WarehouseArea warehouseArea);
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
package com.ruoyi.warehouse.mapper;
|
package com.ruoyi.warehouse.mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
||||||
|
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||||
|
|
||||||
public interface WmsBatchItemMapper
|
public interface WmsBatchItemMapper
|
||||||
{
|
{
|
||||||
public List<WmsBatchItem> selectWmsBatchItemList(WmsBatchItem item);
|
public List<WmsBatchItem> selectWmsBatchItemList(WmsBatchItem item);
|
||||||
public WmsBatchItem selectWmsBatchItemById(Long id);
|
public WmsBatchItem selectWmsBatchItemById(Long id);
|
||||||
public int insertWmsBatchItem(WmsBatchItem item);
|
public int insertWmsBatchItem(WmsBatchItem item);
|
||||||
|
public int insertWmsBatchItemBatch(@Param("items") List<WmsBatchItem> items);
|
||||||
public int updateWmsBatchItem(WmsBatchItem item);
|
public int updateWmsBatchItem(WmsBatchItem item);
|
||||||
public int deleteWmsBatchItemById(Long id);
|
public int deleteWmsBatchItemById(Long id);
|
||||||
public int deleteWmsBatchItemByJobId(Long jobId);
|
public int deleteWmsBatchItemByJobId(Long jobId);
|
||||||
public int countByJobIdAndStatus(WmsBatchItem item);
|
public int countByJobIdAndStatus(WmsBatchItem item);
|
||||||
|
public WmsBatchJob selectWmsBatchItemStats(Long jobId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.warehouse.mapper;
|
package com.ruoyi.warehouse.mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||||
|
|
||||||
public interface WmsBatchJobMapper
|
public interface WmsBatchJobMapper
|
||||||
@ -9,4 +10,5 @@ public interface WmsBatchJobMapper
|
|||||||
public WmsBatchJob selectWmsBatchJobById(Long id);
|
public WmsBatchJob selectWmsBatchJobById(Long id);
|
||||||
public int insertWmsBatchJob(WmsBatchJob job);
|
public int insertWmsBatchJob(WmsBatchJob job);
|
||||||
public int updateWmsBatchJob(WmsBatchJob job);
|
public int updateWmsBatchJob(WmsBatchJob job);
|
||||||
|
public int claimWmsBatchJob(@Param("id") Long id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.warehouse.service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
|
import com.ruoyi.warehouse.domain.OutRecord;
|
||||||
|
import com.ruoyi.warehouse.domain.StockLot;
|
||||||
|
|
||||||
|
public interface IStockLotService
|
||||||
|
{
|
||||||
|
List<StockLot> selectStockLotList(StockLot lot);
|
||||||
|
|
||||||
|
void createInboundLot(InRecord inRecord, String username);
|
||||||
|
|
||||||
|
void allocateOutboundLots(OutRecord outRecord);
|
||||||
|
|
||||||
|
void adjustLots(Long areaId, Long productId, BigDecimal difference, Long checkId, String username);
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import java.text.Normalizer;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -25,21 +26,31 @@ import com.alibaba.fastjson2.JSONArray;
|
|||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
|
import com.ruoyi.warehouse.domain.AiCommand;
|
||||||
import com.ruoyi.warehouse.domain.InRecord;
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
import com.ruoyi.warehouse.domain.OutRecord;
|
import com.ruoyi.warehouse.domain.OutRecord;
|
||||||
import com.ruoyi.warehouse.domain.ProductInfo;
|
import com.ruoyi.warehouse.domain.ProductInfo;
|
||||||
|
import com.ruoyi.warehouse.domain.PurchaseRequest;
|
||||||
import com.ruoyi.warehouse.domain.StockCheckRecord;
|
import com.ruoyi.warehouse.domain.StockCheckRecord;
|
||||||
import com.ruoyi.warehouse.domain.StockInfo;
|
import com.ruoyi.warehouse.domain.StockInfo;
|
||||||
import com.ruoyi.warehouse.domain.WarehouseArea;
|
import com.ruoyi.warehouse.domain.WarehouseArea;
|
||||||
import com.ruoyi.warehouse.domain.agent.AgentCandidate;
|
import com.ruoyi.warehouse.domain.agent.AgentCandidate;
|
||||||
import com.ruoyi.warehouse.domain.agent.AgentPlan;
|
import com.ruoyi.warehouse.domain.agent.AgentPlan;
|
||||||
import com.ruoyi.warehouse.domain.agent.AgentResultItem;
|
import com.ruoyi.warehouse.domain.agent.AgentResultItem;
|
||||||
|
import com.ruoyi.warehouse.mapper.AiCommandMapper;
|
||||||
import com.ruoyi.warehouse.service.IAgentService;
|
import com.ruoyi.warehouse.service.IAgentService;
|
||||||
import com.ruoyi.warehouse.service.IInRecordService;
|
import com.ruoyi.warehouse.service.IInRecordService;
|
||||||
import com.ruoyi.warehouse.service.IOutRecordService;
|
import com.ruoyi.warehouse.service.IOutRecordService;
|
||||||
import com.ruoyi.warehouse.service.IProductInfoService;
|
import com.ruoyi.warehouse.service.IProductInfoService;
|
||||||
|
import com.ruoyi.warehouse.service.IPurchaseRequestService;
|
||||||
import com.ruoyi.warehouse.service.IStockCheckRecordService;
|
import com.ruoyi.warehouse.service.IStockCheckRecordService;
|
||||||
import com.ruoyi.warehouse.service.IStockInfoService;
|
import com.ruoyi.warehouse.service.IStockInfoService;
|
||||||
import com.ruoyi.warehouse.service.IWarehouseAreaService;
|
import com.ruoyi.warehouse.service.IWarehouseAreaService;
|
||||||
@ -53,6 +64,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final String ACTION_QUERY_STOCK = "query_stock";
|
private static final String ACTION_QUERY_STOCK = "query_stock";
|
||||||
|
private static final String ACTION_QUERY_PURCHASE = "query_purchase";
|
||||||
private static final String ACTION_CREATE_IN = "create_in_record";
|
private static final String ACTION_CREATE_IN = "create_in_record";
|
||||||
private static final String ACTION_CONFIRM_IN = "confirm_in_record";
|
private static final String ACTION_CONFIRM_IN = "confirm_in_record";
|
||||||
private static final String ACTION_CREATE_OUT = "create_out_record";
|
private static final String ACTION_CREATE_OUT = "create_out_record";
|
||||||
@ -97,6 +109,9 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IStockInfoService stockInfoService;
|
private IStockInfoService stockInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPurchaseRequestService purchaseRequestService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInRecordService inRecordService;
|
private IInRecordService inRecordService;
|
||||||
|
|
||||||
@ -109,6 +124,15 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISysUserService userService;
|
private ISysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectBudgetService projectBudgetService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AiCommandMapper aiCommandMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AgentPlan analyze(String text, List<AgentResultItem> contextResults, String username)
|
public AgentPlan analyze(String text, List<AgentResultItem> contextResults, String username)
|
||||||
{
|
{
|
||||||
@ -139,27 +163,56 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
plan = fallbackParse(text);
|
plan = fallbackParse(text);
|
||||||
}
|
}
|
||||||
plan.getWarnings().addAll(warnings);
|
plan.getWarnings().addAll(warnings);
|
||||||
return normalizePlan(plan, text, username, contextResults);
|
AgentPlan normalized = normalizePlan(plan, text, username, contextResults);
|
||||||
|
if (normalized.isNeedConfirm() && normalized.isExecutable())
|
||||||
|
{
|
||||||
|
savePendingCommand(normalized, text, username);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public AgentPlan execute(AgentPlan plan, String username)
|
public AgentPlan execute(AgentPlan plan, String username)
|
||||||
{
|
{
|
||||||
if (plan == null || StringUtils.isBlank(plan.getAction()))
|
if (plan == null || plan.getCommandId() == null)
|
||||||
{
|
{
|
||||||
throw new ServiceException("缺少可执行的操作草稿");
|
throw new ServiceException("该操作草稿未在服务器登记,请重新分析后再确认");
|
||||||
}
|
}
|
||||||
AgentPlan checkedPlan = normalizePlan(plan, null, username, null);
|
AiCommand command = aiCommandMapper.selectAiCommandForUpdate(plan.getCommandId(), SecurityUtils.getUserId());
|
||||||
|
if (command == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("操作草稿不存在或不属于当前用户");
|
||||||
|
}
|
||||||
|
if (!"WAIT_CONFIRM".equals(command.getCommandStatus()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("操作草稿已处理,不能重复执行");
|
||||||
|
}
|
||||||
|
if (command.getExpiresAt() == null || command.getExpiresAt().before(new Date()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("操作草稿已过期,请重新分析");
|
||||||
|
}
|
||||||
|
AgentPlan checkedPlan = JSON.parseObject(command.getPlanJson(), AgentPlan.class);
|
||||||
|
if (checkedPlan == null || StringUtils.isBlank(checkedPlan.getAction()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("服务器操作草稿无效");
|
||||||
|
}
|
||||||
|
requireActionPermission(checkedPlan.getAction());
|
||||||
if (!checkedPlan.isExecutable())
|
if (!checkedPlan.isExecutable())
|
||||||
{
|
{
|
||||||
throw new ServiceException(StringUtils.defaultIfBlank(checkedPlan.getMessage(), "当前草稿不能执行"));
|
throw new ServiceException(StringUtils.defaultIfBlank(checkedPlan.getMessage(), "当前草稿不能执行"));
|
||||||
}
|
}
|
||||||
|
if (aiCommandMapper.markExecuting(command.getId(), SecurityUtils.getUserId()) != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("操作草稿已过期或正在执行");
|
||||||
|
}
|
||||||
|
checkedPlan.setCommandId(command.getId());
|
||||||
|
|
||||||
if (ACTION_QUERY_STOCK.equals(checkedPlan.getAction()))
|
if (ACTION_QUERY_STOCK.equals(checkedPlan.getAction()))
|
||||||
{
|
{
|
||||||
checkedPlan.setMessage("查询完成");
|
checkedPlan.setMessage("查询完成");
|
||||||
checkedPlan.setNeedConfirm(false);
|
checkedPlan.setNeedConfirm(false);
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CREATE_IN.equals(checkedPlan.getAction()))
|
if (ACTION_CREATE_IN.equals(checkedPlan.getAction()))
|
||||||
@ -169,6 +222,8 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
record.setProductId(checkedPlan.getProductId());
|
record.setProductId(checkedPlan.getProductId());
|
||||||
record.setInType(checkedPlan.getInType());
|
record.setInType(checkedPlan.getInType());
|
||||||
record.setInNum(checkedPlan.getQuantity());
|
record.setInNum(checkedPlan.getQuantity());
|
||||||
|
record.setSourceProjectId(checkedPlan.getSourceProjectId());
|
||||||
|
record.setExpenseCategoryId(checkedPlan.getExpenseCategoryId());
|
||||||
record.setInOperator(checkedPlan.getOperatorName());
|
record.setInOperator(checkedPlan.getOperatorName());
|
||||||
record.setRemark(checkedPlan.getRemark());
|
record.setRemark(checkedPlan.getRemark());
|
||||||
record.setOperator(username);
|
record.setOperator(username);
|
||||||
@ -177,6 +232,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
checkedPlan.setRecordNo(record.getInNo());
|
checkedPlan.setRecordNo(record.getInNo());
|
||||||
checkedPlan.setMessage("已生成待入库记录:" + record.getInNo());
|
checkedPlan.setMessage("已生成待入库记录:" + record.getInNo());
|
||||||
checkedPlan.setNeedConfirm(false);
|
checkedPlan.setNeedConfirm(false);
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CONFIRM_IN.equals(checkedPlan.getAction()))
|
if (ACTION_CONFIRM_IN.equals(checkedPlan.getAction()))
|
||||||
@ -184,6 +240,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
inRecordService.confirmInRecord(checkedPlan.getRecordId(), username);
|
inRecordService.confirmInRecord(checkedPlan.getRecordId(), username);
|
||||||
checkedPlan.setMessage("已确认入库:" + checkedPlan.getRecordNo());
|
checkedPlan.setMessage("已确认入库:" + checkedPlan.getRecordNo());
|
||||||
checkedPlan.setNeedConfirm(false);
|
checkedPlan.setNeedConfirm(false);
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CREATE_OUT.equals(checkedPlan.getAction()))
|
if (ACTION_CREATE_OUT.equals(checkedPlan.getAction()))
|
||||||
@ -193,6 +250,8 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
record.setProductId(checkedPlan.getProductId());
|
record.setProductId(checkedPlan.getProductId());
|
||||||
record.setOutType(checkedPlan.getOutType());
|
record.setOutType(checkedPlan.getOutType());
|
||||||
record.setOutNum(checkedPlan.getQuantity());
|
record.setOutNum(checkedPlan.getQuantity());
|
||||||
|
record.setUsageProjectId(checkedPlan.getUsageProjectId());
|
||||||
|
record.setCrossProjectReason(checkedPlan.getCrossProjectReason());
|
||||||
record.setOutOperator(checkedPlan.getOperatorName());
|
record.setOutOperator(checkedPlan.getOperatorName());
|
||||||
record.setRemark(checkedPlan.getRemark());
|
record.setRemark(checkedPlan.getRemark());
|
||||||
record.setOperator(username);
|
record.setOperator(username);
|
||||||
@ -201,6 +260,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
checkedPlan.setRecordNo(record.getOutNo());
|
checkedPlan.setRecordNo(record.getOutNo());
|
||||||
checkedPlan.setMessage("已完成出库并生成记录:" + record.getOutNo());
|
checkedPlan.setMessage("已完成出库并生成记录:" + record.getOutNo());
|
||||||
checkedPlan.setNeedConfirm(false);
|
checkedPlan.setNeedConfirm(false);
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CREATE_CHECK.equals(checkedPlan.getAction()))
|
if (ACTION_CREATE_CHECK.equals(checkedPlan.getAction()))
|
||||||
@ -216,6 +276,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
checkedPlan.setRecordNo(record.getCheckNo());
|
checkedPlan.setRecordNo(record.getCheckNo());
|
||||||
checkedPlan.setMessage("已生成待确认盘库记录:" + record.getCheckNo());
|
checkedPlan.setMessage("已生成待确认盘库记录:" + record.getCheckNo());
|
||||||
checkedPlan.setNeedConfirm(false);
|
checkedPlan.setNeedConfirm(false);
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CREATE_PRODUCT.equals(checkedPlan.getAction()))
|
if (ACTION_CREATE_PRODUCT.equals(checkedPlan.getAction()))
|
||||||
@ -238,6 +299,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
normalizePlan(checkedPlan.getNextPlan(), null, username, null);
|
normalizePlan(checkedPlan.getNextPlan(), null, username, null);
|
||||||
checkedPlan.setMessage("已添加产品,并生成下一步草稿");
|
checkedPlan.setMessage("已添加产品,并生成下一步草稿");
|
||||||
}
|
}
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
if (ACTION_CREATE_AREA.equals(checkedPlan.getAction()))
|
if (ACTION_CREATE_AREA.equals(checkedPlan.getAction()))
|
||||||
@ -256,11 +318,96 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
normalizePlan(checkedPlan.getNextPlan(), null, username, null);
|
normalizePlan(checkedPlan.getNextPlan(), null, username, null);
|
||||||
checkedPlan.setMessage("已添加库区,并生成下一步草稿");
|
checkedPlan.setMessage("已添加库区,并生成下一步草稿");
|
||||||
}
|
}
|
||||||
|
finishCommand(command.getId(), checkedPlan);
|
||||||
return checkedPlan;
|
return checkedPlan;
|
||||||
}
|
}
|
||||||
throw new ServiceException("暂不支持该操作");
|
throw new ServiceException("暂不支持该操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void requireActionPermission(String action)
|
||||||
|
{
|
||||||
|
boolean allowed;
|
||||||
|
if (ACTION_QUERY_STOCK.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:stock:list");
|
||||||
|
}
|
||||||
|
else if (ACTION_QUERY_PURCHASE.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:purchase:list") || SecurityUtils.hasPermi("warehouse:purchase:manage");
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_IN.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:in:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_CONFIRM_IN.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:in:confirm");
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_OUT.equals(action))
|
||||||
|
{
|
||||||
|
allowed = (SecurityUtils.hasPermi("warehouse:stock:out") || SecurityUtils.hasPermi("warehouse:out:add"))
|
||||||
|
&& (SecurityUtils.isAdmin() || SecurityUtils.hasPermi("project:inventory:use"));
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_CHECK.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:check:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_PRODUCT.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:product:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_AREA.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:area:add");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allowed = false;
|
||||||
|
}
|
||||||
|
if (!allowed)
|
||||||
|
{
|
||||||
|
throw new ServiceException("无权执行该仓库操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePendingCommand(AgentPlan plan, String requestText, String username)
|
||||||
|
{
|
||||||
|
Date expiresAt = new Date(System.currentTimeMillis() + 10 * 60 * 1000L);
|
||||||
|
AiCommand command = new AiCommand();
|
||||||
|
command.setUserId(SecurityUtils.getUserId());
|
||||||
|
command.setUsername(username);
|
||||||
|
command.setAction(plan.getAction());
|
||||||
|
command.setRequiredPermission(requiredPermission(plan.getAction()));
|
||||||
|
command.setRequestText(StringUtils.substring(requestText, 0, 1000));
|
||||||
|
command.setPlanJson(JSON.toJSONString(plan));
|
||||||
|
command.setCommandStatus("WAIT_CONFIRM");
|
||||||
|
command.setExpiresAt(expiresAt);
|
||||||
|
aiCommandMapper.insertAiCommand(command);
|
||||||
|
plan.setCommandId(command.getId());
|
||||||
|
plan.setExpiresAt(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss", expiresAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishCommand(Long commandId, AgentPlan result)
|
||||||
|
{
|
||||||
|
if (aiCommandMapper.markSucceeded(commandId, JSON.toJSONString(result)) != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("AI 指令执行结果保存失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requiredPermission(String action)
|
||||||
|
{
|
||||||
|
if (ACTION_QUERY_STOCK.equals(action)) return "warehouse:stock:list";
|
||||||
|
if (ACTION_QUERY_PURCHASE.equals(action)) return "warehouse:purchase:list|warehouse:purchase:manage";
|
||||||
|
if (ACTION_CREATE_IN.equals(action)) return "warehouse:in:add";
|
||||||
|
if (ACTION_CONFIRM_IN.equals(action)) return "warehouse:in:confirm";
|
||||||
|
if (ACTION_CREATE_OUT.equals(action)) return "(warehouse:stock:out|warehouse:out:add)&project:inventory:use";
|
||||||
|
if (ACTION_CREATE_CHECK.equals(action)) return "warehouse:check:add";
|
||||||
|
if (ACTION_CREATE_PRODUCT.equals(action)) return "warehouse:product:add";
|
||||||
|
if (ACTION_CREATE_AREA.equals(action)) return "warehouse:area:add";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private AgentPlan normalizePlan(AgentPlan plan, String originalText, String username, List<AgentResultItem> contextResults)
|
private AgentPlan normalizePlan(AgentPlan plan, String originalText, String username, List<AgentResultItem> contextResults)
|
||||||
{
|
{
|
||||||
if (StringUtils.isBlank(plan.getAction()))
|
if (StringUtils.isBlank(plan.getAction()))
|
||||||
@ -268,6 +415,10 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
plan.setAction(ACTION_UNKNOWN);
|
plan.setAction(ACTION_UNKNOWN);
|
||||||
}
|
}
|
||||||
resolveExplicitAction(plan, originalText);
|
resolveExplicitAction(plan, originalText);
|
||||||
|
if (!ACTION_UNKNOWN.equals(plan.getAction()))
|
||||||
|
{
|
||||||
|
requireActionPermission(plan.getAction());
|
||||||
|
}
|
||||||
resolveOperator(plan, originalText, username);
|
resolveOperator(plan, originalText, username);
|
||||||
|
|
||||||
if (requiresOperator(plan.getAction()) && originalText != null)
|
if (requiresOperator(plan.getAction()) && originalText != null)
|
||||||
@ -306,12 +457,29 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
}
|
}
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
if (ACTION_QUERY_PURCHASE.equals(plan.getAction()))
|
||||||
|
{
|
||||||
|
fillPurchaseResults(plan, originalText);
|
||||||
|
plan.setNeedConfirm(false);
|
||||||
|
plan.setExecutable(true);
|
||||||
|
plan.setMessage(plan.getResults().isEmpty() ? "未查询到符合条件的采购申请" : "查询到 " + plan.getResults().size() + " 条采购申请");
|
||||||
|
plan.setSummary("查询采购状态");
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
if (ACTION_CREATE_IN.equals(plan.getAction()) || ACTION_CREATE_OUT.equals(plan.getAction()) || ACTION_CREATE_CHECK.equals(plan.getAction()))
|
if (ACTION_CREATE_IN.equals(plan.getAction()) || ACTION_CREATE_OUT.equals(plan.getAction()) || ACTION_CREATE_CHECK.equals(plan.getAction()))
|
||||||
{
|
{
|
||||||
resolveReferencedResult(plan, originalText, contextResults);
|
resolveReferencedResult(plan, originalText, contextResults);
|
||||||
resolveArea(plan, originalText, true);
|
resolveArea(plan, originalText, true);
|
||||||
resolveProduct(plan, originalText, true);
|
resolveProduct(plan, originalText, true);
|
||||||
|
if (ACTION_CREATE_IN.equals(plan.getAction()))
|
||||||
|
{
|
||||||
|
resolveInboundFunding(plan, originalText);
|
||||||
|
}
|
||||||
|
else if (ACTION_CREATE_OUT.equals(plan.getAction()))
|
||||||
|
{
|
||||||
|
resolveUsageProject(plan, originalText);
|
||||||
|
}
|
||||||
if ((ACTION_CREATE_IN.equals(plan.getAction()) || ACTION_CREATE_CHECK.equals(plan.getAction()))
|
if ((ACTION_CREATE_IN.equals(plan.getAction()) || ACTION_CREATE_CHECK.equals(plan.getAction()))
|
||||||
&& plan.getAreaId() == null && StringUtils.isNotBlank(plan.getAreaName()) && noAreaCandidates(plan))
|
&& plan.getAreaId() == null && StringUtils.isNotBlank(plan.getAreaName()) && noAreaCandidates(plan))
|
||||||
{
|
{
|
||||||
@ -331,7 +499,11 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
}
|
}
|
||||||
validateQuantity(plan);
|
validateQuantity(plan);
|
||||||
plan.setNeedConfirm(true);
|
plan.setNeedConfirm(true);
|
||||||
plan.setExecutable(plan.getAreaId() != null && plan.getProductId() != null && validQuantity(plan) && plan.getCandidates().isEmpty());
|
boolean projectComplete = !ACTION_CREATE_IN.equals(plan.getAction())
|
||||||
|
|| (plan.getSourceProjectId() != null && plan.getExpenseCategoryId() != null);
|
||||||
|
projectComplete = projectComplete && (!ACTION_CREATE_OUT.equals(plan.getAction()) || plan.getUsageProjectId() != null);
|
||||||
|
plan.setExecutable(plan.getAreaId() != null && plan.getProductId() != null && validQuantity(plan)
|
||||||
|
&& projectComplete && plan.getCandidates().isEmpty());
|
||||||
fillCurrentStock(plan);
|
fillCurrentStock(plan);
|
||||||
suggestAlternativeStockAreas(plan);
|
suggestAlternativeStockAreas(plan);
|
||||||
fillSummary(plan);
|
fillSummary(plan);
|
||||||
@ -392,6 +564,138 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resolveInboundFunding(AgentPlan plan, String originalText)
|
||||||
|
{
|
||||||
|
List<ProjectInfo> projects = projectInfoService.selectProjectOptions(null, false);
|
||||||
|
ProjectInfo project = findProject(projects, plan.getSourceProjectId(),
|
||||||
|
StringUtils.defaultString(originalText) + " " + StringUtils.defaultString(plan.getSourceProjectName()));
|
||||||
|
if (project == null && plan.getSourceProjectId() == null && projects.size() == 1)
|
||||||
|
{
|
||||||
|
project = projects.get(0);
|
||||||
|
plan.getWarnings().add("仅有一个可用项目,已自动选择:" + project.getProjectName());
|
||||||
|
}
|
||||||
|
if (project != null)
|
||||||
|
{
|
||||||
|
projectInfoService.requireActiveProject(project.getId());
|
||||||
|
plan.setSourceProjectId(project.getId());
|
||||||
|
plan.setSourceProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plan.setSourceProjectId(null);
|
||||||
|
addProjectCandidates(plan, projects, "source_project");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpenseCategory categoryQuery = new ExpenseCategory();
|
||||||
|
categoryQuery.setStatus(0);
|
||||||
|
List<ExpenseCategory> categories = projectBudgetService.selectExpenseCategoryList(categoryQuery);
|
||||||
|
ExpenseCategory category = findExpenseCategory(categories, plan.getExpenseCategoryId(),
|
||||||
|
StringUtils.defaultString(originalText) + " " + StringUtils.defaultString(plan.getExpenseCategoryName()));
|
||||||
|
if (category != null)
|
||||||
|
{
|
||||||
|
plan.setExpenseCategoryId(category.getId());
|
||||||
|
plan.setExpenseCategoryName(category.getCategoryName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plan.setExpenseCategoryId(null);
|
||||||
|
addExpenseCategoryCandidates(plan, categories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveUsageProject(AgentPlan plan, String originalText)
|
||||||
|
{
|
||||||
|
List<ProjectInfo> projects = projectInfoService.selectProjectOptions(null, false);
|
||||||
|
ProjectInfo project = findProject(projects, plan.getUsageProjectId(),
|
||||||
|
StringUtils.defaultString(originalText) + " " + StringUtils.defaultString(plan.getUsageProjectName()));
|
||||||
|
if (project == null && plan.getUsageProjectId() == null && projects.size() == 1)
|
||||||
|
{
|
||||||
|
project = projects.get(0);
|
||||||
|
plan.getWarnings().add("仅有一个可用项目,已自动选择:" + project.getProjectName());
|
||||||
|
}
|
||||||
|
if (project != null)
|
||||||
|
{
|
||||||
|
projectInfoService.requireActiveProject(project.getId());
|
||||||
|
plan.setUsageProjectId(project.getId());
|
||||||
|
plan.setUsageProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plan.setUsageProjectId(null);
|
||||||
|
addProjectCandidates(plan, projects, "usage_project");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProjectInfo findProject(List<ProjectInfo> projects, Long projectId, String clue)
|
||||||
|
{
|
||||||
|
if (projectId != null)
|
||||||
|
{
|
||||||
|
for (ProjectInfo project : projects)
|
||||||
|
{
|
||||||
|
if (projectId.equals(project.getId())) return project;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ProjectInfo best = null;
|
||||||
|
int bestLength = 0;
|
||||||
|
String normalizedClue = normalizeSearchText(clue);
|
||||||
|
for (ProjectInfo project : projects)
|
||||||
|
{
|
||||||
|
String code = normalizeSearchText(project.getProjectCode());
|
||||||
|
String name = normalizeSearchText(project.getProjectName());
|
||||||
|
int length = normalizedClue.contains(name) ? name.length() : (normalizedClue.contains(code) ? code.length() : 0);
|
||||||
|
if (length > bestLength)
|
||||||
|
{
|
||||||
|
best = project;
|
||||||
|
bestLength = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpenseCategory findExpenseCategory(List<ExpenseCategory> categories, Long categoryId, String clue)
|
||||||
|
{
|
||||||
|
String normalizedClue = normalizeSearchText(clue);
|
||||||
|
for (ExpenseCategory category : categories)
|
||||||
|
{
|
||||||
|
if (categoryId != null && categoryId.equals(category.getId())) return category;
|
||||||
|
if (categoryId == null && (normalizedClue.contains(normalizeSearchText(category.getCategoryName()))
|
||||||
|
|| normalizedClue.contains(normalizeSearchText(category.getCategoryCode()))))
|
||||||
|
{
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProjectCandidates(AgentPlan plan, List<ProjectInfo> projects, String type)
|
||||||
|
{
|
||||||
|
if (hasCandidateType(plan, type)) return;
|
||||||
|
for (ProjectInfo project : first(projects, 10))
|
||||||
|
{
|
||||||
|
plan.getCandidates().add(new AgentCandidate(project.getId(), type, project.getProjectName(), project.getProjectCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addExpenseCategoryCandidates(AgentPlan plan, List<ExpenseCategory> categories)
|
||||||
|
{
|
||||||
|
if (hasCandidateType(plan, "expense_category")) return;
|
||||||
|
for (ExpenseCategory category : first(categories, 10))
|
||||||
|
{
|
||||||
|
plan.getCandidates().add(new AgentCandidate(category.getId(), "expense_category",
|
||||||
|
category.getCategoryName(), category.getCategoryCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCandidateType(AgentPlan plan, String type)
|
||||||
|
{
|
||||||
|
for (AgentCandidate candidate : plan.getCandidates())
|
||||||
|
{
|
||||||
|
if (type.equals(candidate.getType())) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private AgentPlan callModel(String text, List<AgentResultItem> contextResults) throws Exception
|
private AgentPlan callModel(String text, List<AgentResultItem> contextResults) throws Exception
|
||||||
{
|
{
|
||||||
JSONObject body = new JSONObject();
|
JSONObject body = new JSONObject();
|
||||||
@ -440,8 +744,8 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
private String systemPrompt()
|
private String systemPrompt()
|
||||||
{
|
{
|
||||||
return "你是库存管理系统的意图解析器,只返回一个 JSON 对象,不要解释。"
|
return "你是库存管理系统的意图解析器,只返回一个 JSON 对象,不要解释。"
|
||||||
+ "action 只能是 query_stock, create_in_record, confirm_in_record, create_out_record, create_stock_check, create_product, create_area, unknown。"
|
+ "action 只能是 query_stock, query_purchase, create_in_record, confirm_in_record, create_out_record, create_stock_check, create_product, create_area, unknown。"
|
||||||
+ "字段包括 action, areaName, productName, brand, category, spec, unit, quantity, inType, outType, operatorName, recordNo, remark, summary, referenceIndex。"
|
+ "字段包括 action, areaName, productName, brand, category, spec, unit, quantity, inType, outType, operatorName, recordNo, sourceProjectName, expenseCategoryName, usageProjectName, crossProjectReason, remark, summary, referenceIndex。"
|
||||||
+ "只有原文明说第几个、第2条、上一个、刚才那个、这个、那个或它时,referenceIndex 才能返回从 1 开始的序号;‘一个’、‘1个’表示数量,不是引用,没有明确指代必须返回 null。"
|
+ "只有原文明说第几个、第2条、上一个、刚才那个、这个、那个或它时,referenceIndex 才能返回从 1 开始的序号;‘一个’、‘1个’表示数量,不是引用,没有明确指代必须返回 null。"
|
||||||
+ "quantity 只表示本次入库、出库或盘点的数量,规格型号里的数字不算数量,‘一个’表示数量 1。"
|
+ "quantity 只表示本次入库、出库或盘点的数量,规格型号里的数字不算数量,‘一个’表示数量 1。"
|
||||||
+ "类似‘2进6出’、‘8口’的描述优先放入 spec,不要把人员姓名、‘东西’、‘产品’放入 productName。"
|
+ "类似‘2进6出’、‘8口’的描述优先放入 spec,不要把人员姓名、‘东西’、‘产品’放入 productName。"
|
||||||
@ -538,6 +842,68 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillPurchaseResults(AgentPlan plan, String originalText)
|
||||||
|
{
|
||||||
|
PurchaseRequest query = new PurchaseRequest();
|
||||||
|
query.setMaterialName(firstNotBlank(plan.getProductName(), extractPurchaseKeyword(originalText)));
|
||||||
|
List<PurchaseRequest> requests = purchaseRequestService.selectPurchaseRequestList(query);
|
||||||
|
int count = 0;
|
||||||
|
for (PurchaseRequest request : requests)
|
||||||
|
{
|
||||||
|
if (count++ >= 20)
|
||||||
|
{
|
||||||
|
plan.getWarnings().add("采购结果较多,仅展示前 20 条");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AgentResultItem item = new AgentResultItem();
|
||||||
|
item.setId(request.getId());
|
||||||
|
item.setRecordNo(request.getRequestNo());
|
||||||
|
item.setProductId(request.getProductId());
|
||||||
|
item.setProductName(request.getMaterialName());
|
||||||
|
item.setBrand(request.getMaterialBrand());
|
||||||
|
item.setCategory(request.getCategory());
|
||||||
|
item.setSpec(request.getMaterialParams());
|
||||||
|
item.setUnit(request.getUnit());
|
||||||
|
item.setProjectName(request.getProjectName());
|
||||||
|
item.setExpenseCategoryName(request.getExpenseCategoryName());
|
||||||
|
item.setPurchaseStatus(request.getPurchaseStatus());
|
||||||
|
item.setPurchaseStatusName(purchaseStatusName(request.getPurchaseStatus()));
|
||||||
|
item.setQuantity(request.getQuantity());
|
||||||
|
item.setAcceptedQuantity(request.getAcceptedQuantity());
|
||||||
|
item.setPendingArrivalQuantity(request.getPendingArrivalQuantity());
|
||||||
|
item.setEstimatedAmount(request.getEstimatedAmount());
|
||||||
|
if (request.getExpectedDate() != null)
|
||||||
|
{
|
||||||
|
item.setExpectedDate(DateUtils.parseDateToStr("yyyy-MM-dd", request.getExpectedDate()));
|
||||||
|
}
|
||||||
|
plan.getResults().add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractPurchaseKeyword(String text)
|
||||||
|
{
|
||||||
|
String keyword = StringUtils.defaultString(text)
|
||||||
|
.replaceAll("采购|申请|状态|进度|到货|验收|查询|查一下|查", " ")
|
||||||
|
.replaceAll("[,。!??]", " ")
|
||||||
|
.trim();
|
||||||
|
return keyword.length() >= 2 ? keyword : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String purchaseStatusName(Integer status)
|
||||||
|
{
|
||||||
|
if (status == null) return "未知";
|
||||||
|
return switch (status)
|
||||||
|
{
|
||||||
|
case 0 -> "待采购";
|
||||||
|
case 1 -> "已下单";
|
||||||
|
case 2 -> "采购中";
|
||||||
|
case 3 -> "部分到货";
|
||||||
|
case 4 -> "已到齐";
|
||||||
|
case 5 -> "已取消";
|
||||||
|
default -> "未知";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private AgentPlan fallbackParse(String text)
|
private AgentPlan fallbackParse(String text)
|
||||||
{
|
{
|
||||||
String source = StringUtils.defaultString(text);
|
String source = StringUtils.defaultString(text);
|
||||||
@ -556,6 +922,11 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
{
|
{
|
||||||
plan.setAction(ACTION_CONFIRM_IN);
|
plan.setAction(ACTION_CONFIRM_IN);
|
||||||
}
|
}
|
||||||
|
else if (source.contains("采购") && (source.contains("查") || source.contains("状态") || source.contains("进度") || source.contains("到货")))
|
||||||
|
{
|
||||||
|
plan.setAction(ACTION_QUERY_PURCHASE);
|
||||||
|
plan.setProductName(extractPurchaseKeyword(source));
|
||||||
|
}
|
||||||
else if (source.contains("出库"))
|
else if (source.contains("出库"))
|
||||||
{
|
{
|
||||||
plan.setAction(ACTION_CREATE_OUT);
|
plan.setAction(ACTION_CREATE_OUT);
|
||||||
@ -1005,6 +1376,10 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
plan.setAreaName(source.getAreaName());
|
plan.setAreaName(source.getAreaName());
|
||||||
plan.setQuantity(source.getQuantity());
|
plan.setQuantity(source.getQuantity());
|
||||||
plan.setInType(source.getInType());
|
plan.setInType(source.getInType());
|
||||||
|
plan.setSourceProjectId(source.getSourceProjectId());
|
||||||
|
plan.setSourceProjectName(source.getSourceProjectName());
|
||||||
|
plan.setExpenseCategoryId(source.getExpenseCategoryId());
|
||||||
|
plan.setExpenseCategoryName(source.getExpenseCategoryName());
|
||||||
plan.setOperatorName(source.getOperatorName());
|
plan.setOperatorName(source.getOperatorName());
|
||||||
plan.setRemark(source.getRemark());
|
plan.setRemark(source.getRemark());
|
||||||
return plan;
|
return plan;
|
||||||
@ -1025,6 +1400,13 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
plan.setQuantity(source.getQuantity());
|
plan.setQuantity(source.getQuantity());
|
||||||
plan.setInType(source.getInType());
|
plan.setInType(source.getInType());
|
||||||
plan.setOutType(source.getOutType());
|
plan.setOutType(source.getOutType());
|
||||||
|
plan.setSourceProjectId(source.getSourceProjectId());
|
||||||
|
plan.setSourceProjectName(source.getSourceProjectName());
|
||||||
|
plan.setExpenseCategoryId(source.getExpenseCategoryId());
|
||||||
|
plan.setExpenseCategoryName(source.getExpenseCategoryName());
|
||||||
|
plan.setUsageProjectId(source.getUsageProjectId());
|
||||||
|
plan.setUsageProjectName(source.getUsageProjectName());
|
||||||
|
plan.setCrossProjectReason(source.getCrossProjectReason());
|
||||||
plan.setOperatorName(source.getOperatorName());
|
plan.setOperatorName(source.getOperatorName());
|
||||||
plan.setRemark(source.getRemark());
|
plan.setRemark(source.getRemark());
|
||||||
return plan;
|
return plan;
|
||||||
@ -1137,6 +1519,11 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
{
|
{
|
||||||
plan.setAction(ACTION_CONFIRM_IN);
|
plan.setAction(ACTION_CONFIRM_IN);
|
||||||
}
|
}
|
||||||
|
else if (originalText.contains("采购") && (originalText.contains("查") || originalText.contains("状态")
|
||||||
|
|| originalText.contains("进度") || originalText.contains("到货")))
|
||||||
|
{
|
||||||
|
plan.setAction(ACTION_QUERY_PURCHASE);
|
||||||
|
}
|
||||||
else if (originalText.contains("出库"))
|
else if (originalText.contains("出库"))
|
||||||
{
|
{
|
||||||
plan.setAction(ACTION_CREATE_OUT);
|
plan.setAction(ACTION_CREATE_OUT);
|
||||||
@ -1174,13 +1561,13 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
}
|
}
|
||||||
if (matchedUser != null)
|
if (matchedUser != null)
|
||||||
{
|
{
|
||||||
plan.setOperatorName(StringUtils.defaultIfBlank(matchedUser.getNickName(), matchedUser.getUserName()));
|
plan.setOperatorName(matchedUser.getUserName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(plan.getOperatorName()))
|
if (StringUtils.isBlank(plan.getOperatorName()))
|
||||||
{
|
{
|
||||||
plan.setOperatorName(resolveNickName(username));
|
plan.setOperatorName(username);
|
||||||
plan.getWarnings().add("未识别到人员,默认使用当前登录用户");
|
plan.getWarnings().add("未识别到人员,默认使用当前登录用户");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1289,6 +1676,7 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
private void fillActionCandidates(AgentPlan plan)
|
private void fillActionCandidates(AgentPlan plan)
|
||||||
{
|
{
|
||||||
plan.getCandidates().add(new AgentCandidate(1L, "action", "查库存", "例如:查一下路由器库存"));
|
plan.getCandidates().add(new AgentCandidate(1L, "action", "查库存", "例如:查一下路由器库存"));
|
||||||
|
plan.getCandidates().add(new AgentCandidate(7L, "action", "查采购状态", "例如:查询路由器采购进度"));
|
||||||
plan.getCandidates().add(new AgentCandidate(2L, "action", "入库登记", "例如:采购入库路由器10个到一号库"));
|
plan.getCandidates().add(new AgentCandidate(2L, "action", "入库登记", "例如:采购入库路由器10个到一号库"));
|
||||||
plan.getCandidates().add(new AgentCandidate(3L, "action", "出库", "例如:第二个出库1个"));
|
plan.getCandidates().add(new AgentCandidate(3L, "action", "出库", "例如:第二个出库1个"));
|
||||||
plan.getCandidates().add(new AgentCandidate(4L, "action", "盘库", "例如:盘点路由器实际5个"));
|
plan.getCandidates().add(new AgentCandidate(4L, "action", "盘库", "例如:盘点路由器实际5个"));
|
||||||
@ -1661,11 +2049,14 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
String quantity = plan.getQuantity() == null ? "未识别数量" : plan.getQuantity().stripTrailingZeros().toPlainString();
|
String quantity = plan.getQuantity() == null ? "未识别数量" : plan.getQuantity().stripTrailingZeros().toPlainString();
|
||||||
if (ACTION_CREATE_IN.equals(plan.getAction()))
|
if (ACTION_CREATE_IN.equals(plan.getAction()))
|
||||||
{
|
{
|
||||||
plan.setSummary("登记入库:" + area + "," + product + ",数量 " + quantity);
|
plan.setSummary("登记入库:" + area + "," + product + ",数量 " + quantity
|
||||||
|
+ ",来源项目 " + StringUtils.defaultIfBlank(plan.getSourceProjectName(), "未识别")
|
||||||
|
+ ",经费科目 " + StringUtils.defaultIfBlank(plan.getExpenseCategoryName(), "未识别"));
|
||||||
}
|
}
|
||||||
else if (ACTION_CREATE_OUT.equals(plan.getAction()))
|
else if (ACTION_CREATE_OUT.equals(plan.getAction()))
|
||||||
{
|
{
|
||||||
plan.setSummary("执行出库:" + area + "," + product + ",数量 " + quantity);
|
plan.setSummary("执行出库:" + area + "," + product + ",数量 " + quantity
|
||||||
|
+ ",使用项目 " + StringUtils.defaultIfBlank(plan.getUsageProjectName(), "未识别"));
|
||||||
}
|
}
|
||||||
else if (ACTION_CREATE_CHECK.equals(plan.getAction()))
|
else if (ACTION_CREATE_CHECK.equals(plan.getAction()))
|
||||||
{
|
{
|
||||||
@ -1722,20 +2113,6 @@ public class AgentServiceImpl implements IAgentService
|
|||||||
plan.setMessage(plan.isExecutable() ? "请确认是否执行入库" : "该入库单已经确认");
|
plan.setMessage(plan.isExecutable() ? "请确认是否执行入库" : "该入库单已经确认");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveNickName(String username)
|
|
||||||
{
|
|
||||||
if (StringUtils.isBlank(username))
|
|
||||||
{
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
SysUser user = userService.selectUserByUserName(username);
|
|
||||||
if (user != null && StringUtils.isNotBlank(user.getNickName()))
|
|
||||||
{
|
|
||||||
return user.getNickName();
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String firstNotBlank(String... values)
|
private String firstNotBlank(String... values)
|
||||||
{
|
{
|
||||||
for (String value : values)
|
for (String value : values)
|
||||||
|
|||||||
@ -6,14 +6,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.ruoyi.common.constant.UserConstants;
|
import com.ruoyi.common.constant.UserConstants;
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.mapper.ProjectBudgetMapper;
|
||||||
import com.ruoyi.warehouse.domain.InRecord;
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
import com.ruoyi.warehouse.mapper.InRecordMapper;
|
import com.ruoyi.warehouse.mapper.InRecordMapper;
|
||||||
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
||||||
import com.ruoyi.warehouse.service.IInRecordService;
|
import com.ruoyi.warehouse.service.IInRecordService;
|
||||||
|
import com.ruoyi.warehouse.service.IStockLotService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class InRecordServiceImpl implements IInRecordService
|
public class InRecordServiceImpl implements IInRecordService
|
||||||
@ -25,7 +28,13 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
private StockInfoMapper stockInfoMapper;
|
private StockInfoMapper stockInfoMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysUserService userService;
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProjectBudgetMapper projectBudgetMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IStockLotService stockLotService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<InRecord> selectInRecordList(InRecord inRecord)
|
public List<InRecord> selectInRecordList(InRecord inRecord)
|
||||||
@ -50,11 +59,12 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
@Override
|
@Override
|
||||||
public int insertInRecord(InRecord inRecord)
|
public int insertInRecord(InRecord inRecord)
|
||||||
{
|
{
|
||||||
inRecord.setInOperator(resolveNickName(inRecord.getInOperator()));
|
inRecord.setInOperator(StringUtils.trim(inRecord.getInOperator()));
|
||||||
|
requireManualSourceProject(inRecord);
|
||||||
validateInRecord(inRecord);
|
validateInRecord(inRecord);
|
||||||
if (StringUtils.isBlank(inRecord.getInNo()))
|
if (StringUtils.isBlank(inRecord.getInNo()))
|
||||||
{
|
{
|
||||||
inRecord.setInNo("IN" + System.currentTimeMillis());
|
inRecord.setInNo(WarehouseNoUtils.next("IN"));
|
||||||
}
|
}
|
||||||
else if (!checkInNoUnique(inRecord))
|
else if (!checkInNoUnique(inRecord))
|
||||||
{
|
{
|
||||||
@ -67,7 +77,8 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
@Override
|
@Override
|
||||||
public int updateInRecord(InRecord inRecord)
|
public int updateInRecord(InRecord inRecord)
|
||||||
{
|
{
|
||||||
inRecord.setInOperator(resolveNickName(inRecord.getInOperator()));
|
inRecord.setInOperator(StringUtils.trim(inRecord.getInOperator()));
|
||||||
|
requireManualSourceProject(inRecord);
|
||||||
validateInRecord(inRecord);
|
validateInRecord(inRecord);
|
||||||
if (StringUtils.isNotBlank(inRecord.getInNo()) && !checkInNoUnique(inRecord))
|
if (StringUtils.isNotBlank(inRecord.getInNo()) && !checkInNoUnique(inRecord))
|
||||||
{
|
{
|
||||||
@ -84,6 +95,8 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
inRecord.setProductId(dbRecord.getProductId());
|
inRecord.setProductId(dbRecord.getProductId());
|
||||||
inRecord.setPurchaseRequestId(dbRecord.getPurchaseRequestId());
|
inRecord.setPurchaseRequestId(dbRecord.getPurchaseRequestId());
|
||||||
inRecord.setArrivalId(dbRecord.getArrivalId());
|
inRecord.setArrivalId(dbRecord.getArrivalId());
|
||||||
|
inRecord.setSourceProjectId(dbRecord.getSourceProjectId());
|
||||||
|
inRecord.setExpenseCategoryId(dbRecord.getExpenseCategoryId());
|
||||||
inRecord.setSourceNo(dbRecord.getSourceNo());
|
inRecord.setSourceNo(dbRecord.getSourceNo());
|
||||||
inRecord.setInType(dbRecord.getInType());
|
inRecord.setInType(dbRecord.getInType());
|
||||||
inRecord.setInNum(dbRecord.getInNum());
|
inRecord.setInNum(dbRecord.getInNum());
|
||||||
@ -137,7 +150,13 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
confirmRecord.setInStatus(1);
|
confirmRecord.setInStatus(1);
|
||||||
confirmRecord.setOperator(username);
|
confirmRecord.setOperator(username);
|
||||||
confirmRecord.setConfirmTime(new Date());
|
confirmRecord.setConfirmTime(new Date());
|
||||||
return inRecordMapper.confirmInRecord(confirmRecord);
|
int rows = inRecordMapper.confirmInRecord(confirmRecord);
|
||||||
|
if (rows != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("入库记录状态已变化,请刷新后重试");
|
||||||
|
}
|
||||||
|
stockLotService.createInboundLot(inRecord, username);
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateInRecord(InRecord inRecord)
|
private void validateInRecord(InRecord inRecord)
|
||||||
@ -152,17 +171,22 @@ public class InRecordServiceImpl implements IInRecordService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveNickName(String value)
|
private void requireManualSourceProject(InRecord inRecord)
|
||||||
{
|
{
|
||||||
if (StringUtils.isBlank(value))
|
if (inRecord.getArrivalId() != null)
|
||||||
{
|
{
|
||||||
return value;
|
return;
|
||||||
}
|
}
|
||||||
SysUser user = userService.selectUserByUserName(value);
|
if (inRecord.getSourceProjectId() == null || inRecord.getExpenseCategoryId() == null)
|
||||||
if (user != null && StringUtils.isNotBlank(user.getNickName()))
|
|
||||||
{
|
{
|
||||||
return user.getNickName();
|
throw new ServiceException("手工入库必须选择来源项目和经费科目");
|
||||||
|
}
|
||||||
|
projectInfoService.requireActiveProject(inRecord.getSourceProjectId());
|
||||||
|
ExpenseCategory category = projectBudgetMapper.selectExpenseCategoryById(inRecord.getExpenseCategoryId());
|
||||||
|
if (category == null || Integer.valueOf(1).equals(category.getStatus()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目不存在或已停用");
|
||||||
}
|
}
|
||||||
return value.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,15 +6,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.ruoyi.common.constant.UserConstants;
|
import com.ruoyi.common.constant.UserConstants;
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
import com.ruoyi.warehouse.domain.OutRecord;
|
import com.ruoyi.warehouse.domain.OutRecord;
|
||||||
import com.ruoyi.warehouse.domain.StockInfo;
|
import com.ruoyi.warehouse.domain.StockInfo;
|
||||||
import com.ruoyi.warehouse.mapper.OutRecordMapper;
|
import com.ruoyi.warehouse.mapper.OutRecordMapper;
|
||||||
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
||||||
import com.ruoyi.warehouse.service.IOutRecordService;
|
import com.ruoyi.warehouse.service.IOutRecordService;
|
||||||
|
import com.ruoyi.warehouse.service.IStockLotService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class OutRecordServiceImpl implements IOutRecordService
|
public class OutRecordServiceImpl implements IOutRecordService
|
||||||
@ -26,7 +28,10 @@ public class OutRecordServiceImpl implements IOutRecordService
|
|||||||
private StockInfoMapper stockInfoMapper;
|
private StockInfoMapper stockInfoMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISysUserService userService;
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IStockLotService stockLotService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OutRecord> selectOutRecordList(OutRecord outRecord)
|
public List<OutRecord> selectOutRecordList(OutRecord outRecord)
|
||||||
@ -52,7 +57,7 @@ public class OutRecordServiceImpl implements IOutRecordService
|
|||||||
@Transactional
|
@Transactional
|
||||||
public int insertOutRecord(OutRecord outRecord)
|
public int insertOutRecord(OutRecord outRecord)
|
||||||
{
|
{
|
||||||
outRecord.setOutOperator(resolveNickName(outRecord.getOutOperator()));
|
outRecord.setOutOperator(StringUtils.trim(outRecord.getOutOperator()));
|
||||||
if (outRecord.getOutType() == null)
|
if (outRecord.getOutType() == null)
|
||||||
{
|
{
|
||||||
throw new ServiceException("出库类型不能为空");
|
throw new ServiceException("出库类型不能为空");
|
||||||
@ -65,9 +70,18 @@ public class OutRecordServiceImpl implements IOutRecordService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("出库人不能为空");
|
throw new ServiceException("出库人不能为空");
|
||||||
}
|
}
|
||||||
|
if (outRecord.getUsageProjectId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("使用项目不能为空");
|
||||||
|
}
|
||||||
|
if (!SecurityUtils.isAdmin() && !SecurityUtils.hasPermi("project:inventory:use"))
|
||||||
|
{
|
||||||
|
throw new ServiceException("无项目物资使用权限");
|
||||||
|
}
|
||||||
|
projectInfoService.requireActiveProject(outRecord.getUsageProjectId());
|
||||||
if (StringUtils.isBlank(outRecord.getOutNo()))
|
if (StringUtils.isBlank(outRecord.getOutNo()))
|
||||||
{
|
{
|
||||||
outRecord.setOutNo("OUT" + System.currentTimeMillis());
|
outRecord.setOutNo(WarehouseNoUtils.next("OUT"));
|
||||||
}
|
}
|
||||||
else if (!checkOutNoUnique(outRecord))
|
else if (!checkOutNoUnique(outRecord))
|
||||||
{
|
{
|
||||||
@ -83,25 +97,15 @@ public class OutRecordServiceImpl implements IOutRecordService
|
|||||||
throw new ServiceException("库存不足,当前库存:" + stockInfo.getStockNum());
|
throw new ServiceException("库存不足,当前库存:" + stockInfo.getStockNum());
|
||||||
}
|
}
|
||||||
outRecord.setOutTime(new Date());
|
outRecord.setOutTime(new Date());
|
||||||
|
outRecord.setCrossProjectFlag(0);
|
||||||
int rows = stockInfoMapper.deductStock(outRecord.getAreaId(), outRecord.getProductId(), outRecord.getOutNum());
|
int rows = stockInfoMapper.deductStock(outRecord.getAreaId(), outRecord.getProductId(), outRecord.getOutNum());
|
||||||
if (rows <= 0)
|
if (rows <= 0)
|
||||||
{
|
{
|
||||||
throw new ServiceException("库存扣减失败");
|
throw new ServiceException("库存扣减失败");
|
||||||
}
|
}
|
||||||
return outRecordMapper.insertOutRecord(outRecord);
|
int result = outRecordMapper.insertOutRecord(outRecord);
|
||||||
|
stockLotService.allocateOutboundLots(outRecord);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveNickName(String value)
|
|
||||||
{
|
|
||||||
if (StringUtils.isBlank(value))
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
SysUser user = userService.selectUserByUserName(value);
|
|
||||||
if (user != null && StringUtils.isNotBlank(user.getNickName()))
|
|
||||||
{
|
|
||||||
return user.getNickName();
|
|
||||||
}
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,9 @@ import com.ruoyi.warehouse.service.IProductInfoService;
|
|||||||
@Service
|
@Service
|
||||||
public class ProductInfoServiceImpl implements IProductInfoService
|
public class ProductInfoServiceImpl implements IProductInfoService
|
||||||
{
|
{
|
||||||
|
private volatile List<ProductInfo> categoryHistoryCache = List.of();
|
||||||
|
private volatile long categoryHistoryCacheExpiresAt;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ProductInfoMapper productInfoMapper;
|
private ProductInfoMapper productInfoMapper;
|
||||||
|
|
||||||
@ -77,13 +80,17 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
|||||||
{
|
{
|
||||||
normalizeProductInfo(productInfo);
|
normalizeProductInfo(productInfo);
|
||||||
validateProductInfoUnique(productInfo);
|
validateProductInfoUnique(productInfo);
|
||||||
return productInfoMapper.updateProductInfo(productInfo);
|
int rows = productInfoMapper.updateProductInfo(productInfo);
|
||||||
|
invalidateCategoryHistory();
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int deleteProductInfoByIds(Long[] ids)
|
public int deleteProductInfoByIds(Long[] ids)
|
||||||
{
|
{
|
||||||
return productInfoMapper.deleteProductInfoByIds(ids);
|
int rows = productInfoMapper.deleteProductInfoByIds(ids);
|
||||||
|
invalidateCategoryHistory();
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void normalizeProductInfo(ProductInfo productInfo)
|
private void normalizeProductInfo(ProductInfo productInfo)
|
||||||
@ -121,7 +128,7 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
|||||||
private String inferCategoryFromHistory(ProductInfo productInfo)
|
private String inferCategoryFromHistory(ProductInfo productInfo)
|
||||||
{
|
{
|
||||||
Map<String, Integer> scores = new HashMap<>();
|
Map<String, Integer> scores = new HashMap<>();
|
||||||
for (ProductInfo product : productInfoMapper.selectProductInfoAll())
|
for (ProductInfo product : categoryHistory())
|
||||||
{
|
{
|
||||||
if (StringUtils.isBlank(product.getCategory()))
|
if (StringUtils.isBlank(product.getCategory()))
|
||||||
{
|
{
|
||||||
@ -158,6 +165,30 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<ProductInfo> categoryHistory()
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now < categoryHistoryCacheExpiresAt)
|
||||||
|
{
|
||||||
|
return categoryHistoryCache;
|
||||||
|
}
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (now < categoryHistoryCacheExpiresAt)
|
||||||
|
{
|
||||||
|
return categoryHistoryCache;
|
||||||
|
}
|
||||||
|
categoryHistoryCache = List.copyOf(productInfoMapper.selectProductInfoAll());
|
||||||
|
categoryHistoryCacheExpiresAt = now + 60_000L;
|
||||||
|
return categoryHistoryCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidateCategoryHistory()
|
||||||
|
{
|
||||||
|
categoryHistoryCacheExpiresAt = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
private int categoryProductScore(ProductInfo source, ProductInfo target)
|
private int categoryProductScore(ProductInfo source, ProductInfo target)
|
||||||
{
|
{
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
package com.ruoyi.warehouse.service.impl;
|
package com.ruoyi.warehouse.service.impl;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.SimpleDateFormat;
|
import java.math.RoundingMode;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
import com.ruoyi.warehouse.domain.InRecord;
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseArrival;
|
import com.ruoyi.warehouse.domain.PurchaseArrival;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseArrivalAcceptanceRequest;
|
import com.ruoyi.warehouse.domain.PurchaseArrivalAcceptanceRequest;
|
||||||
@ -20,11 +20,11 @@ import com.ruoyi.warehouse.mapper.InRecordMapper;
|
|||||||
import com.ruoyi.warehouse.mapper.PurchaseArrivalMapper;
|
import com.ruoyi.warehouse.mapper.PurchaseArrivalMapper;
|
||||||
import com.ruoyi.warehouse.mapper.PurchaseRequestMapper;
|
import com.ruoyi.warehouse.mapper.PurchaseRequestMapper;
|
||||||
import com.ruoyi.warehouse.service.IPurchaseArrivalService;
|
import com.ruoyi.warehouse.service.IPurchaseArrivalService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
||||||
{
|
{
|
||||||
private static final AtomicInteger ARRIVAL_SEQUENCE = new AtomicInteger();
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PurchaseArrivalMapper purchaseArrivalMapper;
|
private PurchaseArrivalMapper purchaseArrivalMapper;
|
||||||
@ -35,10 +35,14 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private InRecordMapper inRecordMapper;
|
private InRecordMapper inRecordMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectBudgetService projectBudgetService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PurchaseArrival> selectPurchaseArrivalList(Long purchaseRequestId)
|
public List<PurchaseArrival> selectPurchaseArrivalList(Long purchaseRequestId)
|
||||||
{
|
{
|
||||||
if (purchaseRequestMapper.selectPurchaseRequestById(purchaseRequestId, null) == null)
|
PurchaseRequest request = purchaseRequestMapper.selectPurchaseRequestById(purchaseRequestId, null);
|
||||||
|
if (request == null)
|
||||||
{
|
{
|
||||||
throw new ServiceException("采购申请不存在");
|
throw new ServiceException("采购申请不存在");
|
||||||
}
|
}
|
||||||
@ -67,6 +71,15 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("到货日期不能为空");
|
throw new ServiceException("到货日期不能为空");
|
||||||
}
|
}
|
||||||
|
BigDecimal unitPrice = arrival.getActualUnitPrice() == null
|
||||||
|
? request.getEstimatedUnitPrice() : arrival.getActualUnitPrice();
|
||||||
|
if (unitPrice == null || unitPrice.signum() <= 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("本批实际含税单价必须大于0");
|
||||||
|
}
|
||||||
|
arrival.setActualUnitPrice(unitPrice.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
arrival.setActualAmount(arrival.getArrivalQuantity().multiply(arrival.getActualUnitPrice())
|
||||||
|
.setScale(2, RoundingMode.HALF_UP));
|
||||||
BigDecimal activeQuantity = purchaseArrivalMapper.selectActiveQuantityByRequestId(request.getId());
|
BigDecimal activeQuantity = purchaseArrivalMapper.selectActiveQuantityByRequestId(request.getId());
|
||||||
if (activeQuantity.add(arrival.getArrivalQuantity()).compareTo(request.getQuantity()) > 0)
|
if (activeQuantity.add(arrival.getArrivalQuantity()).compareTo(request.getQuantity()) > 0)
|
||||||
{
|
{
|
||||||
@ -116,6 +129,13 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
if (Integer.valueOf(1).equals(arrival.getAcceptanceStatus()))
|
if (Integer.valueOf(1).equals(arrival.getAcceptanceStatus()))
|
||||||
{
|
{
|
||||||
createPendingInRecord(arrival, username);
|
createPendingInRecord(arrival, username);
|
||||||
|
BigDecimal committedRelease = request.getEstimatedUnitPrice()
|
||||||
|
.multiply(arrival.getArrivalQuantity()).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
projectBudgetService.settleExpense(request.getProjectId(), request.getExpenseCategoryId(),
|
||||||
|
committedRelease, arrival.getActualAmount(), "PURCHASE_ARRIVAL_SETTLE", arrival.getId(),
|
||||||
|
arrival.getArrivalNo(), "ARRIVAL:" + arrival.getId() + ":ACCEPT:"
|
||||||
|
+ arrival.getAcceptedTime().getTime(),
|
||||||
|
"采购到货验收转实际支出");
|
||||||
BigDecimal accepted = purchaseArrivalMapper.selectAcceptedQuantityByRequestId(arrival.getPurchaseRequestId());
|
BigDecimal accepted = purchaseArrivalMapper.selectAcceptedQuantityByRequestId(arrival.getPurchaseRequestId());
|
||||||
int status = accepted.compareTo(request.getQuantity()) >= 0 ? 4 : 3;
|
int status = accepted.compareTo(request.getQuantity()) >= 0 ? 4 : 3;
|
||||||
purchaseRequestMapper.updatePurchaseStatus(request.getId(), status,
|
purchaseRequestMapper.updatePurchaseStatus(request.getId(), status,
|
||||||
@ -186,6 +206,12 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
|
|
||||||
if (Integer.valueOf(1).equals(arrival.getAcceptanceStatus()))
|
if (Integer.valueOf(1).equals(arrival.getAcceptanceStatus()))
|
||||||
{
|
{
|
||||||
|
BigDecimal committedRestore = request.getEstimatedUnitPrice()
|
||||||
|
.multiply(arrival.getArrivalQuantity()).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
projectBudgetService.reverseSettlement(request.getProjectId(), request.getExpenseCategoryId(),
|
||||||
|
committedRestore, arrival.getActualAmount(), "PURCHASE_ARRIVAL_REVERSE", arrival.getId(),
|
||||||
|
arrival.getArrivalNo(), "ARRIVAL:" + arrival.getId() + ":REVOKE:" + arrival.getRevokedTime().getTime(),
|
||||||
|
"撤销采购到货验收");
|
||||||
BigDecimal accepted = purchaseArrivalMapper.selectAcceptedQuantityByRequestId(request.getId());
|
BigDecimal accepted = purchaseArrivalMapper.selectAcceptedQuantityByRequestId(request.getId());
|
||||||
int status;
|
int status;
|
||||||
if (accepted.signum() == 0)
|
if (accepted.signum() == 0)
|
||||||
@ -228,6 +254,8 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
inRecord.setProductId(arrival.getProductId());
|
inRecord.setProductId(arrival.getProductId());
|
||||||
inRecord.setPurchaseRequestId(arrival.getPurchaseRequestId());
|
inRecord.setPurchaseRequestId(arrival.getPurchaseRequestId());
|
||||||
inRecord.setArrivalId(arrival.getId());
|
inRecord.setArrivalId(arrival.getId());
|
||||||
|
inRecord.setSourceProjectId(arrival.getProjectId());
|
||||||
|
inRecord.setExpenseCategoryId(arrival.getExpenseCategoryId());
|
||||||
inRecord.setSourceNo(arrival.getArrivalNo());
|
inRecord.setSourceNo(arrival.getArrivalNo());
|
||||||
inRecord.setInType(2);
|
inRecord.setInType(2);
|
||||||
inRecord.setInNum(arrival.getArrivalQuantity());
|
inRecord.setInNum(arrival.getArrivalQuantity());
|
||||||
@ -267,8 +295,6 @@ public class PurchaseArrivalServiceImpl implements IPurchaseArrivalService
|
|||||||
|
|
||||||
private String generateArrivalNo()
|
private String generateArrivalNo()
|
||||||
{
|
{
|
||||||
int sequence = ARRIVAL_SEQUENCE.updateAndGet(value -> value >= 999 ? 0 : value + 1);
|
return WarehouseNoUtils.next("PA");
|
||||||
return "PA" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())
|
|
||||||
+ String.format("%03d", sequence);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
package com.ruoyi.warehouse.service.impl;
|
package com.ruoyi.warehouse.service.impl;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.SimpleDateFormat;
|
import java.math.RoundingMode;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.Objects;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.domain.ProjectInfo;
|
||||||
|
import com.ruoyi.project.mapper.ProjectBudgetMapper;
|
||||||
|
import com.ruoyi.project.mapper.ProjectInfoMapper;
|
||||||
|
import com.ruoyi.project.service.IProjectBudgetService;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
import com.ruoyi.warehouse.domain.ProductInfo;
|
import com.ruoyi.warehouse.domain.ProductInfo;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseRequest;
|
import com.ruoyi.warehouse.domain.PurchaseRequest;
|
||||||
import com.ruoyi.warehouse.domain.PurchaseStatusUpdateRequest;
|
import com.ruoyi.warehouse.domain.PurchaseStatusUpdateRequest;
|
||||||
@ -19,11 +24,11 @@ import com.ruoyi.warehouse.mapper.PurchaseArrivalMapper;
|
|||||||
import com.ruoyi.warehouse.mapper.PurchaseRequestMapper;
|
import com.ruoyi.warehouse.mapper.PurchaseRequestMapper;
|
||||||
import com.ruoyi.warehouse.service.IProductInfoService;
|
import com.ruoyi.warehouse.service.IProductInfoService;
|
||||||
import com.ruoyi.warehouse.service.IPurchaseRequestService;
|
import com.ruoyi.warehouse.service.IPurchaseRequestService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
||||||
{
|
{
|
||||||
private static final AtomicInteger REQUEST_SEQUENCE = new AtomicInteger();
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PurchaseRequestMapper purchaseRequestMapper;
|
private PurchaseRequestMapper purchaseRequestMapper;
|
||||||
|
|
||||||
@ -36,6 +41,18 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private PurchaseArrivalMapper purchaseArrivalMapper;
|
private PurchaseArrivalMapper purchaseArrivalMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProjectInfoMapper projectInfoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProjectBudgetMapper projectBudgetMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectBudgetService projectBudgetService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<PurchaseRequest> selectPurchaseRequestList(PurchaseRequest purchaseRequest)
|
public List<PurchaseRequest> selectPurchaseRequestList(PurchaseRequest purchaseRequest)
|
||||||
{
|
{
|
||||||
@ -53,6 +70,7 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
public int insertPurchaseRequest(PurchaseRequest purchaseRequest)
|
public int insertPurchaseRequest(PurchaseRequest purchaseRequest)
|
||||||
{
|
{
|
||||||
normalize(purchaseRequest);
|
normalize(purchaseRequest);
|
||||||
|
resolveFunding(purchaseRequest);
|
||||||
validateRequest(purchaseRequest);
|
validateRequest(purchaseRequest);
|
||||||
purchaseRequest.setApplicantId(SecurityUtils.getUserId());
|
purchaseRequest.setApplicantId(SecurityUtils.getUserId());
|
||||||
purchaseRequest.setApplicantName(SecurityUtils.getUsername());
|
purchaseRequest.setApplicantName(SecurityUtils.getUsername());
|
||||||
@ -69,7 +87,12 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
throw new ServiceException("申请单号已存在");
|
throw new ServiceException("申请单号已存在");
|
||||||
}
|
}
|
||||||
resolveProduct(purchaseRequest);
|
resolveProduct(purchaseRequest);
|
||||||
return purchaseRequestMapper.insertPurchaseRequest(purchaseRequest);
|
int rows = purchaseRequestMapper.insertPurchaseRequest(purchaseRequest);
|
||||||
|
projectBudgetService.changeCommitment(purchaseRequest.getProjectId(), purchaseRequest.getExpenseCategoryId(),
|
||||||
|
purchaseRequest.getEstimatedAmount(), "PURCHASE_COMMIT", purchaseRequest.getId(),
|
||||||
|
purchaseRequest.getRequestNo(), "PURCHASE:" + purchaseRequest.getId() + ":CREATE",
|
||||||
|
"采购申请占用经费");
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -83,6 +106,7 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
PurchaseRequest existing = requireRequest(purchaseRequest.getId());
|
PurchaseRequest existing = requireRequest(purchaseRequest.getId());
|
||||||
checkOwnerOrManager(existing);
|
checkOwnerOrManager(existing);
|
||||||
normalize(purchaseRequest);
|
normalize(purchaseRequest);
|
||||||
|
resolveFunding(purchaseRequest);
|
||||||
validateRequest(purchaseRequest);
|
validateRequest(purchaseRequest);
|
||||||
|
|
||||||
BigDecimal activeArrivalQuantity = purchaseArrivalMapper.selectActiveQuantityByRequestId(existing.getId());
|
BigDecimal activeArrivalQuantity = purchaseArrivalMapper.selectActiveQuantityByRequestId(existing.getId());
|
||||||
@ -94,6 +118,13 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("已有到货记录,不能更换采购物料");
|
throw new ServiceException("已有到货记录,不能更换采购物料");
|
||||||
}
|
}
|
||||||
|
if (activeArrivalQuantity.signum() > 0
|
||||||
|
&& (!Objects.equals(existing.getProjectId(), purchaseRequest.getProjectId())
|
||||||
|
|| !Objects.equals(existing.getExpenseCategoryId(), purchaseRequest.getExpenseCategoryId())
|
||||||
|
|| money(existing.getEstimatedUnitPrice()).compareTo(money(purchaseRequest.getEstimatedUnitPrice())) != 0))
|
||||||
|
{
|
||||||
|
throw new ServiceException("已有到货记录,不能更换项目、经费科目或预计单价");
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(purchaseRequest.getRequestNo()))
|
if (StringUtils.isBlank(purchaseRequest.getRequestNo()))
|
||||||
{
|
{
|
||||||
@ -119,6 +150,7 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("无权修改其他员工的采购申请");
|
throw new ServiceException("无权修改其他员工的采购申请");
|
||||||
}
|
}
|
||||||
|
adjustPurchaseCommitment(existing, purchaseRequest);
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +170,12 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("申请 " + request.getRequestNo() + " 已有到货记录,不能删除");
|
throw new ServiceException("申请 " + request.getRequestNo() + " 已有到货记录,不能删除");
|
||||||
}
|
}
|
||||||
|
if (!Integer.valueOf(5).equals(request.getPurchaseStatus()))
|
||||||
|
{
|
||||||
|
projectBudgetService.changeCommitment(request.getProjectId(), request.getExpenseCategoryId(),
|
||||||
|
money(request.getEstimatedAmount()).negate(), "PURCHASE_DELETE_RELEASE", request.getId(),
|
||||||
|
request.getRequestNo(), "PURCHASE:" + request.getId() + ":DELETE", "删除采购申请释放经费");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int rows = purchaseRequestMapper.deletePurchaseRequestByIds(ids, currentApplicantScope());
|
int rows = purchaseRequestMapper.deletePurchaseRequestByIds(ids, currentApplicantScope());
|
||||||
if (rows != ids.length)
|
if (rows != ids.length)
|
||||||
@ -166,6 +204,18 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("已有待验收或已验收到货,不能取消采购申请");
|
throw new ServiceException("已有待验收或已验收到货,不能取消采购申请");
|
||||||
}
|
}
|
||||||
|
if (!Integer.valueOf(5).equals(existing.getPurchaseStatus()) && Integer.valueOf(5).equals(status))
|
||||||
|
{
|
||||||
|
projectBudgetService.changeCommitment(existing.getProjectId(), existing.getExpenseCategoryId(),
|
||||||
|
money(existing.getEstimatedAmount()).negate(), "PURCHASE_CANCEL_RELEASE", existing.getId(),
|
||||||
|
existing.getRequestNo(), "PURCHASE:" + existing.getId() + ":CANCEL", "取消采购释放经费");
|
||||||
|
}
|
||||||
|
else if (Integer.valueOf(5).equals(existing.getPurchaseStatus()) && !Integer.valueOf(5).equals(status))
|
||||||
|
{
|
||||||
|
projectBudgetService.changeCommitment(existing.getProjectId(), existing.getExpenseCategoryId(),
|
||||||
|
money(existing.getEstimatedAmount()), "PURCHASE_REOPEN_COMMIT", existing.getId(),
|
||||||
|
existing.getRequestNo(), null, "恢复采购重新占用经费");
|
||||||
|
}
|
||||||
return purchaseRequestMapper.updatePurchaseStatus(id, status,
|
return purchaseRequestMapper.updatePurchaseStatus(id, status,
|
||||||
trimToNull(request.getProgressRemark()), SecurityUtils.getUsername());
|
trimToNull(request.getProgressRemark()), SecurityUtils.getUsername());
|
||||||
}
|
}
|
||||||
@ -258,6 +308,8 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
private void normalize(PurchaseRequest request)
|
private void normalize(PurchaseRequest request)
|
||||||
{
|
{
|
||||||
request.setRequestNo(StringUtils.trim(request.getRequestNo()));
|
request.setRequestNo(StringUtils.trim(request.getRequestNo()));
|
||||||
|
request.setProjectCode(trimToNull(request.getProjectCode()));
|
||||||
|
request.setExpenseCategoryCode(trimToNull(request.getExpenseCategoryCode()));
|
||||||
request.setProductCode(trimToNull(request.getProductCode()));
|
request.setProductCode(trimToNull(request.getProductCode()));
|
||||||
request.setMaterialName(StringUtils.trim(request.getMaterialName()));
|
request.setMaterialName(StringUtils.trim(request.getMaterialName()));
|
||||||
request.setMaterialBrand(trimToNull(request.getMaterialBrand()));
|
request.setMaterialBrand(trimToNull(request.getMaterialBrand()));
|
||||||
@ -282,6 +334,14 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("需求数量必须大于0");
|
throw new ServiceException("需求数量必须大于0");
|
||||||
}
|
}
|
||||||
|
if (request.getProjectId() == null || request.getExpenseCategoryId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("采购项目和经费科目不能为空");
|
||||||
|
}
|
||||||
|
if (request.getEstimatedUnitPrice() == null || request.getEstimatedUnitPrice().signum() <= 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("预计含税单价必须大于0");
|
||||||
|
}
|
||||||
if (StringUtils.isNotBlank(request.getPurchaseLink())
|
if (StringUtils.isNotBlank(request.getPurchaseLink())
|
||||||
&& !request.getPurchaseLink().matches("(?i)^https?://.+"))
|
&& !request.getPurchaseLink().matches("(?i)^https?://.+"))
|
||||||
{
|
{
|
||||||
@ -289,6 +349,82 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resolveFunding(PurchaseRequest request)
|
||||||
|
{
|
||||||
|
ProjectInfo project = null;
|
||||||
|
if (request.getProjectId() != null)
|
||||||
|
{
|
||||||
|
project = projectInfoMapper.selectProjectInfoById(request.getProjectId(), null);
|
||||||
|
}
|
||||||
|
else if (StringUtils.isNotBlank(request.getProjectCode()))
|
||||||
|
{
|
||||||
|
project = projectInfoMapper.selectProjectInfoByCode(request.getProjectCode());
|
||||||
|
}
|
||||||
|
if (project == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("采购项目不存在");
|
||||||
|
}
|
||||||
|
projectInfoService.requireActiveProject(project.getId());
|
||||||
|
|
||||||
|
ExpenseCategory category = null;
|
||||||
|
if (request.getExpenseCategoryId() != null)
|
||||||
|
{
|
||||||
|
category = projectBudgetMapper.selectExpenseCategoryById(request.getExpenseCategoryId());
|
||||||
|
}
|
||||||
|
else if (StringUtils.isNotBlank(request.getExpenseCategoryCode()))
|
||||||
|
{
|
||||||
|
category = projectBudgetMapper.selectExpenseCategoryByCode(request.getExpenseCategoryCode());
|
||||||
|
}
|
||||||
|
if (category == null || Integer.valueOf(1).equals(category.getStatus()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("经费科目不存在或已停用");
|
||||||
|
}
|
||||||
|
request.setProjectId(project.getId());
|
||||||
|
request.setProjectCode(project.getProjectCode());
|
||||||
|
request.setProjectName(project.getProjectName());
|
||||||
|
request.setExpenseCategoryId(category.getId());
|
||||||
|
request.setExpenseCategoryCode(category.getCategoryCode());
|
||||||
|
request.setExpenseCategoryName(category.getCategoryName());
|
||||||
|
request.setEstimatedUnitPrice(money(request.getEstimatedUnitPrice()));
|
||||||
|
request.setEstimatedAmount(request.getQuantity().multiply(request.getEstimatedUnitPrice())
|
||||||
|
.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustPurchaseCommitment(PurchaseRequest existing, PurchaseRequest updated)
|
||||||
|
{
|
||||||
|
if (Integer.valueOf(5).equals(existing.getPurchaseStatus()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BigDecimal oldAmount = money(existing.getEstimatedAmount());
|
||||||
|
BigDecimal newAmount = money(updated.getEstimatedAmount());
|
||||||
|
if (!Objects.equals(existing.getProjectId(), updated.getProjectId())
|
||||||
|
|| !Objects.equals(existing.getExpenseCategoryId(), updated.getExpenseCategoryId()))
|
||||||
|
{
|
||||||
|
if (oldAmount.signum() != 0)
|
||||||
|
{
|
||||||
|
projectBudgetService.changeCommitment(existing.getProjectId(), existing.getExpenseCategoryId(),
|
||||||
|
oldAmount.negate(), "PURCHASE_MOVE_RELEASE", existing.getId(), existing.getRequestNo(),
|
||||||
|
null, "采购申请更换项目或科目,释放原占用");
|
||||||
|
}
|
||||||
|
projectBudgetService.changeCommitment(updated.getProjectId(), updated.getExpenseCategoryId(),
|
||||||
|
newAmount, "PURCHASE_MOVE_COMMIT", updated.getId(), updated.getRequestNo(),
|
||||||
|
null, "采购申请更换项目或科目,重新占用");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BigDecimal delta = newAmount.subtract(oldAmount);
|
||||||
|
if (delta.signum() != 0)
|
||||||
|
{
|
||||||
|
projectBudgetService.changeCommitment(updated.getProjectId(), updated.getExpenseCategoryId(), delta,
|
||||||
|
"PURCHASE_COMMIT_ADJUST", updated.getId(), updated.getRequestNo(), null, "调整采购预计金额");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal money(BigDecimal value)
|
||||||
|
{
|
||||||
|
return value == null ? BigDecimal.ZERO.setScale(2) : value.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
private void resolveProduct(PurchaseRequest request)
|
private void resolveProduct(PurchaseRequest request)
|
||||||
{
|
{
|
||||||
ProductInfo draft = new ProductInfo();
|
ProductInfo draft = new ProductInfo();
|
||||||
@ -404,8 +540,6 @@ public class PurchaseRequestServiceImpl implements IPurchaseRequestService
|
|||||||
|
|
||||||
private String generateRequestNo()
|
private String generateRequestNo()
|
||||||
{
|
{
|
||||||
int sequence = REQUEST_SEQUENCE.updateAndGet(value -> value >= 999 ? 0 : value + 1);
|
return WarehouseNoUtils.next("PR");
|
||||||
return "PR" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())
|
|
||||||
+ String.format("%03d", sequence);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,8 +13,10 @@ import java.util.Base64;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||||
import org.apache.poi.ss.usermodel.FormulaEvaluator;
|
import org.apache.poi.ss.usermodel.FormulaEvaluator;
|
||||||
@ -24,6 +26,7 @@ import org.apache.poi.ss.usermodel.Workbook;
|
|||||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@ -35,6 +38,7 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.warehouse.domain.ProductInfo;
|
import com.ruoyi.warehouse.domain.ProductInfo;
|
||||||
import com.ruoyi.warehouse.service.IProductInfoService;
|
import com.ruoyi.warehouse.service.IProductInfoService;
|
||||||
import com.ruoyi.warehouse.service.IPurchaseSmartImportService;
|
import com.ruoyi.warehouse.service.IPurchaseSmartImportService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseFileUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PurchaseSmartImportServiceImpl implements IPurchaseSmartImportService
|
public class PurchaseSmartImportServiceImpl implements IPurchaseSmartImportService
|
||||||
@ -59,6 +63,10 @@ public class PurchaseSmartImportServiceImpl implements IPurchaseSmartImportServi
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IProductInfoService productService;
|
private IProductInfoService productService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("warehouseTaskExecutor")
|
||||||
|
private Executor warehouseTaskExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject analyze(MultipartFile file) throws Exception
|
public JSONObject analyze(MultipartFile file) throws Exception
|
||||||
{
|
{
|
||||||
@ -176,7 +184,7 @@ public class PurchaseSmartImportServiceImpl implements IPurchaseSmartImportServi
|
|||||||
{
|
{
|
||||||
throw new CompletionException(e);
|
throw new CompletionException(e);
|
||||||
}
|
}
|
||||||
}));
|
}, warehouseTaskExecutor));
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -745,12 +753,6 @@ public class PurchaseSmartImportServiceImpl implements IPurchaseSmartImportServi
|
|||||||
|
|
||||||
private void validateFile(MultipartFile file)
|
private void validateFile(MultipartFile file)
|
||||||
{
|
{
|
||||||
if (file == null || file.isEmpty()) throw new ServiceException("请选择图片或Excel文件");
|
WarehouseFileUtils.validateUpload(file, MAX_FILE_SIZE, Set.of("xls", "xlsx", "png", "jpg", "jpeg", "webp"));
|
||||||
if (file.getSize() > MAX_FILE_SIZE) throw new ServiceException("文件不能超过10MB");
|
|
||||||
String extension = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase(Locale.ROOT);
|
|
||||||
if (!List.of("xls", "xlsx", "png", "jpg", "jpeg", "webp").contains(extension))
|
|
||||||
{
|
|
||||||
throw new ServiceException("仅支持 xls、xlsx、png、jpg、jpeg、webp 文件");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import com.ruoyi.warehouse.domain.StockInfo;
|
|||||||
import com.ruoyi.warehouse.mapper.StockCheckRecordMapper;
|
import com.ruoyi.warehouse.mapper.StockCheckRecordMapper;
|
||||||
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
||||||
import com.ruoyi.warehouse.service.IStockCheckRecordService;
|
import com.ruoyi.warehouse.service.IStockCheckRecordService;
|
||||||
|
import com.ruoyi.warehouse.service.IStockLotService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class StockCheckRecordServiceImpl implements IStockCheckRecordService
|
public class StockCheckRecordServiceImpl implements IStockCheckRecordService
|
||||||
@ -24,6 +26,9 @@ public class StockCheckRecordServiceImpl implements IStockCheckRecordService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private StockInfoMapper stockInfoMapper;
|
private StockInfoMapper stockInfoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IStockLotService stockLotService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<StockCheckRecord> selectStockCheckRecordList(StockCheckRecord stockCheckRecord)
|
public List<StockCheckRecord> selectStockCheckRecordList(StockCheckRecord stockCheckRecord)
|
||||||
{
|
{
|
||||||
@ -53,7 +58,7 @@ public class StockCheckRecordServiceImpl implements IStockCheckRecordService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isBlank(stockCheckRecord.getCheckNo()))
|
if (StringUtils.isBlank(stockCheckRecord.getCheckNo()))
|
||||||
{
|
{
|
||||||
stockCheckRecord.setCheckNo("CHK" + System.currentTimeMillis());
|
stockCheckRecord.setCheckNo(WarehouseNoUtils.next("CHK"));
|
||||||
}
|
}
|
||||||
else if (!checkCheckNoUnique(stockCheckRecord))
|
else if (!checkCheckNoUnique(stockCheckRecord))
|
||||||
{
|
{
|
||||||
@ -110,6 +115,11 @@ public class StockCheckRecordServiceImpl implements IStockCheckRecordService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("该盘点记录已生效");
|
throw new ServiceException("该盘点记录已生效");
|
||||||
}
|
}
|
||||||
|
StockInfo currentStock = stockInfoMapper.selectStockInfoByAreaProductForUpdate(
|
||||||
|
checkRecord.getAreaId(), checkRecord.getProductId());
|
||||||
|
BigDecimal currentQuantity = currentStock == null ? BigDecimal.ZERO : currentStock.getStockNum();
|
||||||
|
stockLotService.adjustLots(checkRecord.getAreaId(), checkRecord.getProductId(),
|
||||||
|
checkRecord.getRealNum().subtract(currentQuantity), checkRecord.getId(), username);
|
||||||
stockInfoMapper.upsertSetStock(checkRecord.getAreaId(), checkRecord.getProductId(), checkRecord.getRealNum());
|
stockInfoMapper.upsertSetStock(checkRecord.getAreaId(), checkRecord.getProductId(), checkRecord.getRealNum());
|
||||||
StockCheckRecord confirmRecord = new StockCheckRecord();
|
StockCheckRecord confirmRecord = new StockCheckRecord();
|
||||||
confirmRecord.setId(id);
|
confirmRecord.setId(id);
|
||||||
|
|||||||
@ -0,0 +1,189 @@
|
|||||||
|
package com.ruoyi.warehouse.service.impl;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.project.domain.ExpenseCategory;
|
||||||
|
import com.ruoyi.project.mapper.ProjectBudgetMapper;
|
||||||
|
import com.ruoyi.project.service.IProjectInfoService;
|
||||||
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
|
import com.ruoyi.warehouse.domain.OutRecord;
|
||||||
|
import com.ruoyi.warehouse.domain.PurchaseArrival;
|
||||||
|
import com.ruoyi.warehouse.domain.StockLot;
|
||||||
|
import com.ruoyi.warehouse.mapper.OutRecordMapper;
|
||||||
|
import com.ruoyi.warehouse.mapper.PurchaseArrivalMapper;
|
||||||
|
import com.ruoyi.warehouse.mapper.StockLotMapper;
|
||||||
|
import com.ruoyi.warehouse.service.IStockLotService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StockLotServiceImpl implements IStockLotService
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private StockLotMapper stockLotMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OutRecordMapper outRecordMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PurchaseArrivalMapper purchaseArrivalMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProjectBudgetMapper projectBudgetMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IProjectInfoService projectInfoService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StockLot> selectStockLotList(StockLot lot)
|
||||||
|
{
|
||||||
|
return stockLotMapper.selectStockLotList(lot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createInboundLot(InRecord inRecord, String username)
|
||||||
|
{
|
||||||
|
if (stockLotMapper.selectStockLotByInRecordId(inRecord.getId()) != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Long sourceProjectId = inRecord.getSourceProjectId();
|
||||||
|
Long categoryId = inRecord.getExpenseCategoryId();
|
||||||
|
if (sourceProjectId == null)
|
||||||
|
{
|
||||||
|
sourceProjectId = projectInfoService.selectLegacyProjectId();
|
||||||
|
}
|
||||||
|
if (categoryId == null)
|
||||||
|
{
|
||||||
|
ExpenseCategory legacy = projectBudgetMapper.selectExpenseCategoryByCode("LEGACY");
|
||||||
|
categoryId = legacy == null ? null : legacy.getId();
|
||||||
|
}
|
||||||
|
BigDecimal unitCost = BigDecimal.ZERO.setScale(2);
|
||||||
|
if (inRecord.getArrivalId() != null)
|
||||||
|
{
|
||||||
|
PurchaseArrival arrival = purchaseArrivalMapper.selectPurchaseArrivalById(inRecord.getArrivalId());
|
||||||
|
if (arrival != null && arrival.getActualUnitPrice() != null)
|
||||||
|
{
|
||||||
|
unitCost = arrival.getActualUnitPrice().setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StockLot lot = new StockLot();
|
||||||
|
lot.setLotNo(WarehouseNoUtils.next("LOT"));
|
||||||
|
lot.setAreaId(inRecord.getAreaId());
|
||||||
|
lot.setProductId(inRecord.getProductId());
|
||||||
|
lot.setSourceProjectId(sourceProjectId);
|
||||||
|
lot.setExpenseCategoryId(categoryId);
|
||||||
|
lot.setPurchaseRequestId(inRecord.getPurchaseRequestId());
|
||||||
|
lot.setArrivalId(inRecord.getArrivalId());
|
||||||
|
lot.setInRecordId(inRecord.getId());
|
||||||
|
lot.setReceivedQuantity(inRecord.getInNum());
|
||||||
|
lot.setAvailableQuantity(inRecord.getInNum());
|
||||||
|
lot.setUnitCost(unitCost);
|
||||||
|
lot.setLotStatus(0);
|
||||||
|
lot.setRemark("入库单:" + inRecord.getInNo());
|
||||||
|
lot.setCreateBy(username);
|
||||||
|
stockLotMapper.insertStockLot(lot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allocateOutboundLots(OutRecord outRecord)
|
||||||
|
{
|
||||||
|
if (outRecord.getUsageProjectId() == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("使用项目不能为空");
|
||||||
|
}
|
||||||
|
projectInfoService.requireActiveProject(outRecord.getUsageProjectId());
|
||||||
|
List<StockLot> lots = stockLotMapper.selectAvailableLotsForUpdate(
|
||||||
|
outRecord.getAreaId(), outRecord.getProductId());
|
||||||
|
BigDecimal remaining = outRecord.getOutNum();
|
||||||
|
Long legacyProjectId = projectInfoService.selectLegacyProjectId();
|
||||||
|
boolean crossProject = false;
|
||||||
|
for (StockLot lot : lots)
|
||||||
|
{
|
||||||
|
if (remaining.signum() <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BigDecimal allocated = remaining.min(lot.getAvailableQuantity());
|
||||||
|
boolean currentCross = !lot.getSourceProjectId().equals(outRecord.getUsageProjectId())
|
||||||
|
&& !lot.getSourceProjectId().equals(legacyProjectId);
|
||||||
|
crossProject = crossProject || currentCross;
|
||||||
|
if (stockLotMapper.deductStockLot(lot.getId(), allocated) != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("库存批次已变化,请刷新后重试");
|
||||||
|
}
|
||||||
|
BigDecimal unitCost = lot.getUnitCost() == null ? BigDecimal.ZERO : lot.getUnitCost();
|
||||||
|
stockLotMapper.insertOutRecordLot(outRecord.getId(), lot.getId(), lot.getSourceProjectId(),
|
||||||
|
outRecord.getUsageProjectId(), allocated, unitCost,
|
||||||
|
allocated.multiply(unitCost).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
remaining = remaining.subtract(allocated);
|
||||||
|
}
|
||||||
|
if (remaining.signum() > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("库存批次数量不足,缺少:" + remaining);
|
||||||
|
}
|
||||||
|
if (crossProject)
|
||||||
|
{
|
||||||
|
if (!SecurityUtils.isAdmin() && !SecurityUtils.hasPermi("project:inventory:cross-use"))
|
||||||
|
{
|
||||||
|
throw new ServiceException("库存来源项目与使用项目不同,无跨项目使用权限");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(outRecord.getCrossProjectReason()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("跨项目使用必须填写原因");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outRecord.setCrossProjectFlag(crossProject ? 1 : 0);
|
||||||
|
outRecordMapper.updateOutRecordProjectFlags(outRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void adjustLots(Long areaId, Long productId, BigDecimal difference, Long checkId, String username)
|
||||||
|
{
|
||||||
|
if (difference == null || difference.signum() == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (difference.signum() > 0)
|
||||||
|
{
|
||||||
|
ExpenseCategory legacy = projectBudgetMapper.selectExpenseCategoryByCode("LEGACY");
|
||||||
|
StockLot lot = new StockLot();
|
||||||
|
lot.setLotNo(WarehouseNoUtils.next("ADJ"));
|
||||||
|
lot.setAreaId(areaId);
|
||||||
|
lot.setProductId(productId);
|
||||||
|
lot.setSourceProjectId(projectInfoService.selectLegacyProjectId());
|
||||||
|
lot.setExpenseCategoryId(legacy == null ? null : legacy.getId());
|
||||||
|
lot.setReceivedQuantity(difference);
|
||||||
|
lot.setAvailableQuantity(difference);
|
||||||
|
lot.setUnitCost(BigDecimal.ZERO.setScale(2));
|
||||||
|
lot.setLotStatus(0);
|
||||||
|
lot.setRemark("盘库盘盈,盘库记录ID:" + checkId);
|
||||||
|
lot.setCreateBy(username);
|
||||||
|
stockLotMapper.insertStockLot(lot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BigDecimal remaining = difference.abs();
|
||||||
|
for (StockLot lot : stockLotMapper.selectAvailableLotsForUpdate(areaId, productId))
|
||||||
|
{
|
||||||
|
if (remaining.signum() <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BigDecimal allocated = remaining.min(lot.getAvailableQuantity());
|
||||||
|
if (stockLotMapper.deductStockLot(lot.getId(), allocated) != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("盘库调整时库存批次已变化,请刷新后重试");
|
||||||
|
}
|
||||||
|
remaining = remaining.subtract(allocated);
|
||||||
|
}
|
||||||
|
if (remaining.signum() > 0)
|
||||||
|
{
|
||||||
|
throw new ServiceException("库存批次与汇总库存不一致,无法完成盘亏调整");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,10 +16,12 @@ import java.time.Duration;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||||
@ -28,6 +30,7 @@ import org.apache.poi.ss.usermodel.Sheet;
|
|||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@ -37,6 +40,7 @@ import com.alibaba.fastjson2.JSONObject;
|
|||||||
import com.ruoyi.common.config.RuoYiConfig;
|
import com.ruoyi.common.config.RuoYiConfig;
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.system.service.ISysUserService;
|
import com.ruoyi.system.service.ISysUserService;
|
||||||
import com.ruoyi.warehouse.domain.InRecord;
|
import com.ruoyi.warehouse.domain.InRecord;
|
||||||
@ -49,6 +53,8 @@ import com.ruoyi.warehouse.domain.WmsBatchItem;
|
|||||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||||
import com.ruoyi.warehouse.mapper.WmsBatchItemMapper;
|
import com.ruoyi.warehouse.mapper.WmsBatchItemMapper;
|
||||||
import com.ruoyi.warehouse.mapper.WmsBatchJobMapper;
|
import com.ruoyi.warehouse.mapper.WmsBatchJobMapper;
|
||||||
|
import com.ruoyi.warehouse.mapper.ProductInfoMapper;
|
||||||
|
import com.ruoyi.warehouse.mapper.WarehouseAreaMapper;
|
||||||
import com.ruoyi.warehouse.service.IInRecordService;
|
import com.ruoyi.warehouse.service.IInRecordService;
|
||||||
import com.ruoyi.warehouse.service.IOutRecordService;
|
import com.ruoyi.warehouse.service.IOutRecordService;
|
||||||
import com.ruoyi.warehouse.service.IProductInfoService;
|
import com.ruoyi.warehouse.service.IProductInfoService;
|
||||||
@ -56,10 +62,18 @@ import com.ruoyi.warehouse.service.IStockCheckRecordService;
|
|||||||
import com.ruoyi.warehouse.service.IStockInfoService;
|
import com.ruoyi.warehouse.service.IStockInfoService;
|
||||||
import com.ruoyi.warehouse.service.IWarehouseAreaService;
|
import com.ruoyi.warehouse.service.IWarehouseAreaService;
|
||||||
import com.ruoyi.warehouse.service.IWmsBatchImportService;
|
import com.ruoyi.warehouse.service.IWmsBatchImportService;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseFileUtils;
|
||||||
|
import com.ruoyi.warehouse.util.WarehouseNoUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
||||||
{
|
{
|
||||||
|
private static final long MAX_UPLOAD_SIZE = 10L * 1024 * 1024;
|
||||||
|
private static final int MAX_BATCH_ITEMS = 2000;
|
||||||
|
private static final int DB_BATCH_SIZE = 100;
|
||||||
|
private static final Set<String> ALLOWED_EXTENSIONS = Set.of("xls", "xlsx", "jpg", "jpeg", "png", "bmp");
|
||||||
|
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
|
||||||
|
.version(HttpClient.Version.HTTP_1_1).build();
|
||||||
private static final String ACTION_AREA = "create_area";
|
private static final String ACTION_AREA = "create_area";
|
||||||
private static final String ACTION_PRODUCT = "create_product";
|
private static final String ACTION_PRODUCT = "create_product";
|
||||||
private static final String ACTION_IN = "create_in_record";
|
private static final String ACTION_IN = "create_in_record";
|
||||||
@ -79,6 +93,9 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
private static final int ITEM_DONE = 3;
|
private static final int ITEM_DONE = 3;
|
||||||
private static final int ITEM_FAILED = 4;
|
private static final int ITEM_FAILED = 4;
|
||||||
|
|
||||||
|
private volatile Map<String, String> userAccountCache = Map.of();
|
||||||
|
private volatile long userAccountCacheExpiresAt;
|
||||||
|
|
||||||
@Value("${agent.model.base-url}")
|
@Value("${agent.model.base-url}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
|
||||||
@ -94,6 +111,12 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private WmsBatchItemMapper itemMapper;
|
private WmsBatchItemMapper itemMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WarehouseAreaMapper areaMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductInfoMapper productMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IWarehouseAreaService areaService;
|
private IWarehouseAreaService areaService;
|
||||||
|
|
||||||
@ -115,20 +138,16 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISysUserService userService;
|
private ISysUserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("warehouseTaskExecutor")
|
||||||
|
private Executor warehouseTaskExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WmsBatchJob uploadExcel(MultipartFile file, String username) throws Exception
|
public WmsBatchJob uploadExcel(MultipartFile file, String username) throws Exception
|
||||||
{
|
{
|
||||||
if (file == null || file.isEmpty())
|
String extension = WarehouseFileUtils.validateUpload(file, MAX_UPLOAD_SIZE, ALLOWED_EXTENSIONS);
|
||||||
{
|
|
||||||
throw new ServiceException("请选择要上传的文件");
|
|
||||||
}
|
|
||||||
String extension = StringUtils.defaultString(FilenameUtils.getExtension(file.getOriginalFilename())).toLowerCase(Locale.ROOT);
|
|
||||||
if (!isExcel(extension) && !isImage(extension))
|
|
||||||
{
|
|
||||||
throw new ServiceException("当前支持 xls/xlsx/jpg/jpeg/png/bmp 文件");
|
|
||||||
}
|
|
||||||
|
|
||||||
String jobNo = "BATCH" + System.currentTimeMillis();
|
String jobNo = WarehouseNoUtils.next("BATCH");
|
||||||
String safeName = jobNo + "." + extension;
|
String safeName = jobNo + "." + extension;
|
||||||
Path dir = Paths.get(RuoYiConfig.getUploadPath(), "wms-batch").toAbsolutePath().normalize();
|
Path dir = Paths.get(RuoYiConfig.getUploadPath(), "wms-batch").toAbsolutePath().normalize();
|
||||||
Files.createDirectories(dir);
|
Files.createDirectories(dir);
|
||||||
@ -154,11 +173,11 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
|
|
||||||
if (isExcel(extension))
|
if (isExcel(extension))
|
||||||
{
|
{
|
||||||
CompletableFuture.runAsync(() -> parseExcelJob(job.getId(), username));
|
warehouseTaskExecutor.execute(() -> parseExcelJob(job.getId(), username));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CompletableFuture.runAsync(() -> parseImageJob(job.getId(), username));
|
warehouseTaskExecutor.execute(() -> parseImageJob(job.getId(), username));
|
||||||
}
|
}
|
||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
@ -166,18 +185,23 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
@Override
|
@Override
|
||||||
public List<WmsBatchJob> selectJobList(WmsBatchJob job)
|
public List<WmsBatchJob> selectJobList(WmsBatchJob job)
|
||||||
{
|
{
|
||||||
|
if (!SecurityUtils.isAdmin())
|
||||||
|
{
|
||||||
|
job.setCreateBy(SecurityUtils.getUsername());
|
||||||
|
}
|
||||||
return jobMapper.selectWmsBatchJobList(job);
|
return jobMapper.selectWmsBatchJobList(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WmsBatchJob selectJobById(Long id)
|
public WmsBatchJob selectJobById(Long id)
|
||||||
{
|
{
|
||||||
return jobMapper.selectWmsBatchJobById(id);
|
return requireAccessibleJob(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WmsBatchItem> selectItemList(Long jobId)
|
public List<WmsBatchItem> selectItemList(Long jobId)
|
||||||
{
|
{
|
||||||
|
requireAccessibleJob(jobId);
|
||||||
WmsBatchItem query = new WmsBatchItem();
|
WmsBatchItem query = new WmsBatchItem();
|
||||||
query.setJobId(jobId);
|
query.setJobId(jobId);
|
||||||
return itemMapper.selectWmsBatchItemList(query);
|
return itemMapper.selectWmsBatchItemList(query);
|
||||||
@ -215,10 +239,11 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
||||||
List<WmsBatchItem> items = selectItemList(jobId);
|
List<WmsBatchItem> items = selectItemList(jobId);
|
||||||
|
Set<Long> selectedIds = itemIds == null || itemIds.isEmpty() ? null : new HashSet<>(itemIds);
|
||||||
int rows = 0;
|
int rows = 0;
|
||||||
for (WmsBatchItem item : items)
|
for (WmsBatchItem item : items)
|
||||||
{
|
{
|
||||||
if (itemIds != null && !itemIds.isEmpty() && !itemIds.contains(item.getId()))
|
if (selectedIds != null && !selectedIds.contains(item.getId()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -257,11 +282,7 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
@Override
|
@Override
|
||||||
public WmsBatchJob confirmJob(Long jobId, String username)
|
public WmsBatchJob confirmJob(Long jobId, String username)
|
||||||
{
|
{
|
||||||
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
WmsBatchJob job = requireAccessibleJob(jobId);
|
||||||
if (job == null)
|
|
||||||
{
|
|
||||||
throw new ServiceException("批量任务不存在");
|
|
||||||
}
|
|
||||||
if (!Integer.valueOf(JOB_WAIT_CONFIRM).equals(job.getStatus()))
|
if (!Integer.valueOf(JOB_WAIT_CONFIRM).equals(job.getStatus()))
|
||||||
{
|
{
|
||||||
throw new ServiceException("当前任务还不能确认执行");
|
throw new ServiceException("当前任务还不能确认执行");
|
||||||
@ -274,8 +295,15 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
throw new ServiceException("存在校验失败的明细,请先修改或删除后再确认");
|
throw new ServiceException("存在校验失败的明细,请先修改或删除后再确认");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateJob(jobId, JOB_EXECUTING, 0, null);
|
|
||||||
List<WmsBatchItem> items = selectItemList(jobId);
|
List<WmsBatchItem> items = selectItemList(jobId);
|
||||||
|
for (WmsBatchItem item : items)
|
||||||
|
{
|
||||||
|
requireActionPermission(item.getActionType());
|
||||||
|
}
|
||||||
|
if (jobMapper.claimWmsBatchJob(jobId) != 1)
|
||||||
|
{
|
||||||
|
throw new ServiceException("任务状态已变化,请刷新后重试");
|
||||||
|
}
|
||||||
int success = 0;
|
int success = 0;
|
||||||
int fail = 0;
|
int fail = 0;
|
||||||
int total = items.size();
|
int total = items.size();
|
||||||
@ -296,7 +324,10 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
fail++;
|
fail++;
|
||||||
}
|
}
|
||||||
itemMapper.updateWmsBatchItem(item);
|
itemMapper.updateWmsBatchItem(item);
|
||||||
updateJobProgress(jobId, JOB_EXECUTING, total, i + 1, success, fail);
|
if ((i + 1) % 10 == 0 || i + 1 == total)
|
||||||
|
{
|
||||||
|
updateJobProgress(jobId, JOB_EXECUTING, total, i + 1, success, fail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
WmsBatchJob finish = new WmsBatchJob();
|
WmsBatchJob finish = new WmsBatchJob();
|
||||||
finish.setId(jobId);
|
finish.setId(jobId);
|
||||||
@ -331,19 +362,18 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
normalizeItem(item);
|
normalizeItem(item);
|
||||||
validateItem(item, username);
|
validateItem(item, username);
|
||||||
fillParsedJson(item);
|
fillParsedJson(item);
|
||||||
itemMapper.insertWmsBatchItem(item);
|
|
||||||
if ((i + 1) % 10 == 0 || i + 1 == total)
|
|
||||||
{
|
|
||||||
int progress = 10 + (int) Math.floor((i + 1) * 80.0 / total);
|
|
||||||
refreshJobCount(jobId, JOB_PARSING, progress, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
insertItemsInBatches(items);
|
||||||
refreshJobCount(jobId, JOB_WAIT_CONFIRM, 100, null);
|
refreshJobCount(jobId, JOB_WAIT_CONFIRM, 100, null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
updateJob(jobId, JOB_FAILED, 100, StringUtils.defaultIfBlank(e.getMessage(), "解析失败"));
|
updateJob(jobId, JOB_FAILED, 100, StringUtils.defaultIfBlank(e.getMessage(), "解析失败"));
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deleteJobSource(jobId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseImageJob(Long jobId, String username)
|
private void parseImageJob(Long jobId, String username)
|
||||||
@ -367,16 +397,18 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
normalizeItem(item);
|
normalizeItem(item);
|
||||||
validateItem(item, username);
|
validateItem(item, username);
|
||||||
fillParsedJson(item);
|
fillParsedJson(item);
|
||||||
itemMapper.insertWmsBatchItem(item);
|
|
||||||
int progress = 20 + (int) Math.floor((i + 1) * 70.0 / total);
|
|
||||||
refreshJobCount(jobId, JOB_PARSING, progress, null);
|
|
||||||
}
|
}
|
||||||
|
insertItemsInBatches(items);
|
||||||
refreshJobCount(jobId, JOB_WAIT_CONFIRM, 100, null);
|
refreshJobCount(jobId, JOB_WAIT_CONFIRM, 100, null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
updateJob(jobId, JOB_FAILED, 100, StringUtils.defaultIfBlank(e.getMessage(), "图片解析失败"));
|
updateJob(jobId, JOB_FAILED, 100, StringUtils.defaultIfBlank(e.getMessage(), "图片解析失败"));
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
deleteJobSource(jobId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<WmsBatchItem> parseImage(WmsBatchJob job, String username) throws Exception
|
private List<WmsBatchItem> parseImage(WmsBatchJob job, String username) throws Exception
|
||||||
@ -416,6 +448,10 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
WmsBatchItem item = rowToItem(row, headerMap, formatter, sheetAction, username);
|
WmsBatchItem item = rowToItem(row, headerMap, formatter, sheetAction, username);
|
||||||
item.setRowNo(rowIndex + 1);
|
item.setRowNo(rowIndex + 1);
|
||||||
items.add(item);
|
items.add(item);
|
||||||
|
if (items.size() > MAX_BATCH_ITEMS)
|
||||||
|
{
|
||||||
|
throw new ServiceException("单次最多导入" + MAX_BATCH_ITEMS + "条数据");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
@ -425,7 +461,7 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
{
|
{
|
||||||
WmsBatchItem item = new WmsBatchItem();
|
WmsBatchItem item = new WmsBatchItem();
|
||||||
item.setActionType(sheetAction);
|
item.setActionType(sheetAction);
|
||||||
item.setOperatorName(resolveNickName(username));
|
item.setOperatorName(username);
|
||||||
StringBuilder raw = new StringBuilder();
|
StringBuilder raw = new StringBuilder();
|
||||||
for (Map.Entry<Integer, String> entry : headerMap.entrySet())
|
for (Map.Entry<Integer, String> entry : headerMap.entrySet())
|
||||||
{
|
{
|
||||||
@ -464,6 +500,10 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
{
|
{
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
if (rows.size() > MAX_BATCH_ITEMS)
|
||||||
|
{
|
||||||
|
throw new ServiceException("单次最多导入" + MAX_BATCH_ITEMS + "条数据");
|
||||||
|
}
|
||||||
for (int i = 0; i < rows.size(); i++)
|
for (int i = 0; i < rows.size(); i++)
|
||||||
{
|
{
|
||||||
JSONObject row = rows.getJSONObject(i);
|
JSONObject row = rows.getJSONObject(i);
|
||||||
@ -484,7 +524,7 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
item.setOperatorName(trimToNull(firstText(row, "operatorName", "operator", "入库人", "出库人", "盘点人", "操作人")));
|
item.setOperatorName(trimToNull(firstText(row, "operatorName", "operator", "入库人", "出库人", "盘点人", "操作人")));
|
||||||
if (StringUtils.isBlank(item.getOperatorName()))
|
if (StringUtils.isBlank(item.getOperatorName()))
|
||||||
{
|
{
|
||||||
item.setOperatorName(resolveNickName(username));
|
item.setOperatorName(username);
|
||||||
}
|
}
|
||||||
item.setTypeCode(parseTypeCode(firstText(row, "typeCode", "inType", "outType", "类型", "入库类型", "出库类型"), item.getActionType()));
|
item.setTypeCode(parseTypeCode(firstText(row, "typeCode", "inType", "outType", "类型", "入库类型", "出库类型"), item.getActionType()));
|
||||||
item.setSourceNo(trimToNull(firstText(row, "sourceNo", "来源单据号", "关联单据号")));
|
item.setSourceNo(trimToNull(firstText(row, "sourceNo", "来源单据号", "关联单据号")));
|
||||||
@ -575,15 +615,13 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
body.put("max_tokens", 8192);
|
body.put("max_tokens", 8192);
|
||||||
body.put("stream", false);
|
body.put("stream", false);
|
||||||
|
|
||||||
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
|
|
||||||
.version(HttpClient.Version.HTTP_1_1).build();
|
|
||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(normalizeEndpoint(baseUrl)))
|
.uri(URI.create(normalizeEndpoint(baseUrl)))
|
||||||
.timeout(Duration.ofSeconds(120))
|
.timeout(Duration.ofSeconds(120))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(body.toJSONString(), StandardCharsets.UTF_8));
|
.POST(HttpRequest.BodyPublishers.ofString(body.toJSONString(), StandardCharsets.UTF_8));
|
||||||
if (StringUtils.isNotBlank(apiKey)) requestBuilder.header("Authorization", "Bearer " + apiKey);
|
if (StringUtils.isNotBlank(apiKey)) requestBuilder.header("Authorization", "Bearer " + apiKey);
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
HttpResponse<String> response = HTTP_CLIENT.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
if (response.statusCode() < 200 || response.statusCode() >= 300)
|
if (response.statusCode() < 200 || response.statusCode() >= 300)
|
||||||
{
|
{
|
||||||
throw new ServiceException("图片模型解析失败,HTTP " + response.statusCode());
|
throw new ServiceException("图片模型解析失败,HTTP " + response.statusCode());
|
||||||
@ -738,7 +776,11 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isBlank(item.getOperatorName()))
|
if (StringUtils.isBlank(item.getOperatorName()))
|
||||||
{
|
{
|
||||||
item.setOperatorName(resolveNickName(username));
|
item.setOperatorName(username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.setOperatorName(resolveUserName(item.getOperatorName()));
|
||||||
}
|
}
|
||||||
if (findArea(item.getAreaName()) == null)
|
if (findArea(item.getAreaName()) == null)
|
||||||
{
|
{
|
||||||
@ -759,7 +801,11 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isBlank(item.getOperatorName()))
|
if (StringUtils.isBlank(item.getOperatorName()))
|
||||||
{
|
{
|
||||||
item.setOperatorName(resolveNickName(username));
|
item.setOperatorName(username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.setOperatorName(resolveUserName(item.getOperatorName()));
|
||||||
}
|
}
|
||||||
WarehouseArea area = findArea(item.getAreaName());
|
WarehouseArea area = findArea(item.getAreaName());
|
||||||
ProductInfo product = findProduct(item);
|
ProductInfo product = findProduct(item);
|
||||||
@ -793,7 +839,11 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isBlank(item.getOperatorName()))
|
if (StringUtils.isBlank(item.getOperatorName()))
|
||||||
{
|
{
|
||||||
item.setOperatorName(resolveNickName(username));
|
item.setOperatorName(username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.setOperatorName(resolveUserName(item.getOperatorName()));
|
||||||
}
|
}
|
||||||
if (findArea(item.getAreaName()) == null)
|
if (findArea(item.getAreaName()) == null)
|
||||||
{
|
{
|
||||||
@ -899,15 +949,58 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ensureJobEditable(Long jobId)
|
private void ensureJobEditable(Long jobId)
|
||||||
|
{
|
||||||
|
WmsBatchJob job = requireAccessibleJob(jobId);
|
||||||
|
if (Integer.valueOf(JOB_EXECUTING).equals(job.getStatus()) || Integer.valueOf(JOB_DONE).equals(job.getStatus()))
|
||||||
|
{
|
||||||
|
throw new ServiceException("任务已执行,不能再修改明细");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WmsBatchJob requireAccessibleJob(Long jobId)
|
||||||
{
|
{
|
||||||
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
||||||
if (job == null)
|
if (job == null)
|
||||||
{
|
{
|
||||||
throw new ServiceException("批量任务不存在");
|
throw new ServiceException("批量任务不存在");
|
||||||
}
|
}
|
||||||
if (Integer.valueOf(JOB_EXECUTING).equals(job.getStatus()) || Integer.valueOf(JOB_DONE).equals(job.getStatus()))
|
if (!SecurityUtils.isAdmin() && !SecurityUtils.getUsername().equals(job.getCreateBy()))
|
||||||
{
|
{
|
||||||
throw new ServiceException("任务已执行,不能再修改明细");
|
throw new ServiceException("无权访问其他用户的批量任务");
|
||||||
|
}
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireActionPermission(String action)
|
||||||
|
{
|
||||||
|
boolean allowed;
|
||||||
|
if (ACTION_AREA.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:area:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_PRODUCT.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:product:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_IN.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:in:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_OUT.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:stock:out") || SecurityUtils.hasPermi("warehouse:out:add");
|
||||||
|
}
|
||||||
|
else if (ACTION_CHECK.equals(action))
|
||||||
|
{
|
||||||
|
allowed = SecurityUtils.hasPermi("warehouse:check:add");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allowed = false;
|
||||||
|
}
|
||||||
|
if (!allowed)
|
||||||
|
{
|
||||||
|
throw new ServiceException("批量任务中包含无权执行的操作:" + action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -948,14 +1041,7 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (WarehouseArea area : areaService.selectWarehouseAreaAll())
|
return areaMapper.selectWarehouseAreaByNameOrCode(areaName.trim());
|
||||||
{
|
|
||||||
if (sameText(area.getAreaName(), areaName) || sameText(area.getAreaCode(), areaName))
|
|
||||||
{
|
|
||||||
return area;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProductInfo findProduct(WmsBatchItem item)
|
private ProductInfo findProduct(WmsBatchItem item)
|
||||||
@ -964,41 +1050,41 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (ProductInfo product : productService.selectProductInfoAll())
|
ProductInfo query = new ProductInfo();
|
||||||
{
|
query.setProductName(item.getProductName());
|
||||||
if (sameText(product.getProductName(), item.getProductName())
|
query.setBrand(item.getBrand());
|
||||||
&& sameText(product.getBrand(), item.getBrand())
|
query.setCategory(item.getCategory());
|
||||||
&& sameText(product.getCategory(), item.getCategory())
|
query.setSpec(item.getSpec());
|
||||||
&& sameText(product.getSpec(), item.getSpec()))
|
return productMapper.checkProductInfoUnique(query);
|
||||||
{
|
|
||||||
return product;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshJobCount(Long jobId, Integer status, Integer progress, String errorMsg)
|
private void refreshJobCount(Long jobId, Integer status, Integer progress, String errorMsg)
|
||||||
{
|
{
|
||||||
WmsBatchItem validQuery = new WmsBatchItem();
|
WmsBatchJob stats = itemMapper.selectWmsBatchItemStats(jobId);
|
||||||
validQuery.setJobId(jobId);
|
int total = stats == null || stats.getTotalCount() == null ? 0 : stats.getTotalCount();
|
||||||
validQuery.setStatus(ITEM_VALID);
|
int valid = stats == null || stats.getSuccessCount() == null ? 0 : stats.getSuccessCount();
|
||||||
WmsBatchItem invalidQuery = new WmsBatchItem();
|
int invalid = stats == null || stats.getFailCount() == null ? 0 : stats.getFailCount();
|
||||||
invalidQuery.setJobId(jobId);
|
|
||||||
invalidQuery.setStatus(ITEM_INVALID);
|
|
||||||
int valid = itemMapper.countByJobIdAndStatus(validQuery);
|
|
||||||
int invalid = itemMapper.countByJobIdAndStatus(invalidQuery);
|
|
||||||
|
|
||||||
WmsBatchJob job = new WmsBatchJob();
|
WmsBatchJob job = new WmsBatchJob();
|
||||||
job.setId(jobId);
|
job.setId(jobId);
|
||||||
job.setStatus(status);
|
job.setStatus(status);
|
||||||
job.setProgress(progress);
|
job.setProgress(progress);
|
||||||
job.setTotalCount(valid + invalid);
|
job.setTotalCount(total);
|
||||||
job.setSuccessCount(valid);
|
job.setSuccessCount(valid);
|
||||||
job.setFailCount(invalid);
|
job.setFailCount(invalid);
|
||||||
job.setErrorMsg(errorMsg);
|
job.setErrorMsg(errorMsg);
|
||||||
jobMapper.updateWmsBatchJob(job);
|
jobMapper.updateWmsBatchJob(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void insertItemsInBatches(List<WmsBatchItem> items)
|
||||||
|
{
|
||||||
|
for (int start = 0; start < items.size(); start += DB_BATCH_SIZE)
|
||||||
|
{
|
||||||
|
int end = Math.min(start + DB_BATCH_SIZE, items.size());
|
||||||
|
itemMapper.insertWmsBatchItemBatch(items.subList(start, end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateJob(Long jobId, Integer status, Integer progress, String errorMsg)
|
private void updateJob(Long jobId, Integer status, Integer progress, String errorMsg)
|
||||||
{
|
{
|
||||||
WmsBatchJob job = new WmsBatchJob();
|
WmsBatchJob job = new WmsBatchJob();
|
||||||
@ -1021,6 +1107,28 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
jobMapper.updateWmsBatchJob(job);
|
jobMapper.updateWmsBatchJob(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteJobSource(Long jobId)
|
||||||
|
{
|
||||||
|
WmsBatchJob job = jobMapper.selectWmsBatchJobById(jobId);
|
||||||
|
if (job == null || StringUtils.isBlank(job.getFilePath()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Path uploadDir = Paths.get(RuoYiConfig.getUploadPath(), "wms-batch").toAbsolutePath().normalize();
|
||||||
|
Path source = Paths.get(job.getFilePath()).toAbsolutePath().normalize();
|
||||||
|
if (source.startsWith(uploadDir))
|
||||||
|
{
|
||||||
|
Files.deleteIfExists(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ignored)
|
||||||
|
{
|
||||||
|
// 文件清理由后续运维任务兜底,不影响任务结果。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int findHeaderRow(Sheet sheet, DataFormatter formatter)
|
private int findHeaderRow(Sheet sheet, DataFormatter formatter)
|
||||||
{
|
{
|
||||||
int max = Math.min(sheet.getLastRowNum(), 10);
|
int max = Math.min(sheet.getLastRowNum(), 10);
|
||||||
@ -1386,18 +1494,47 @@ public class WmsBatchImportServiceImpl implements IWmsBatchImportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveNickName(String value)
|
private String resolveUserName(String value)
|
||||||
{
|
{
|
||||||
if (StringUtils.isBlank(value))
|
if (StringUtils.isBlank(value))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
SysUser user = userService.selectUserByUserName(value);
|
String trimmed = value.trim();
|
||||||
if (user != null && StringUtils.isNotBlank(user.getNickName()))
|
return userAccountMap().getOrDefault(trimmed.toLowerCase(Locale.ROOT), trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> userAccountMap()
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now < userAccountCacheExpiresAt)
|
||||||
{
|
{
|
||||||
return user.getNickName();
|
return userAccountCache;
|
||||||
|
}
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (now < userAccountCacheExpiresAt)
|
||||||
|
{
|
||||||
|
return userAccountCache;
|
||||||
|
}
|
||||||
|
Map<String, String> accounts = new HashMap<>();
|
||||||
|
SysUser query = new SysUser();
|
||||||
|
query.setStatus("0");
|
||||||
|
for (SysUser user : userService.selectUserList(query))
|
||||||
|
{
|
||||||
|
if (StringUtils.isNotBlank(user.getUserName()))
|
||||||
|
{
|
||||||
|
accounts.put(user.getUserName().trim().toLowerCase(Locale.ROOT), user.getUserName());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(user.getNickName()))
|
||||||
|
{
|
||||||
|
accounts.putIfAbsent(user.getNickName().trim().toLowerCase(Locale.ROOT), user.getUserName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userAccountCache = Map.copyOf(accounts);
|
||||||
|
userAccountCacheExpiresAt = now + 60_000L;
|
||||||
|
return userAccountCache;
|
||||||
}
|
}
|
||||||
return value.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean sameText(String left, String right)
|
private boolean sameText(String left, String right)
|
||||||
|
|||||||
@ -0,0 +1,144 @@
|
|||||||
|
package com.ruoyi.warehouse.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
public final class WarehouseFileUtils
|
||||||
|
{
|
||||||
|
private static final long MAX_IMAGE_PIXELS = 40_000_000L;
|
||||||
|
|
||||||
|
private WarehouseFileUtils()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String validateUpload(MultipartFile file, long maxFileSize, Set<String> allowedExtensions)
|
||||||
|
{
|
||||||
|
if (file == null || file.isEmpty())
|
||||||
|
{
|
||||||
|
throw new ServiceException("请选择要上传的文件");
|
||||||
|
}
|
||||||
|
if (file.getSize() > maxFileSize)
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件不能超过" + (maxFileSize / 1024 / 1024) + "MB");
|
||||||
|
}
|
||||||
|
String extension = StringUtils.defaultString(FilenameUtils.getExtension(file.getOriginalFilename()))
|
||||||
|
.toLowerCase(Locale.ROOT);
|
||||||
|
if (!allowedExtensions.contains(extension))
|
||||||
|
{
|
||||||
|
throw new ServiceException("不支持的文件类型");
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] header;
|
||||||
|
try (InputStream input = file.getInputStream())
|
||||||
|
{
|
||||||
|
header = input.readNBytes(12);
|
||||||
|
}
|
||||||
|
if ("xls".equals(extension) && !startsWith(header, 0xD0, 0xCF, 0x11, 0xE0))
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件内容不是有效的Excel文件");
|
||||||
|
}
|
||||||
|
if ("xlsx".equals(extension) && !startsWith(header, 0x50, 0x4B))
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件内容不是有效的Excel文件");
|
||||||
|
}
|
||||||
|
if (isImage(extension))
|
||||||
|
{
|
||||||
|
validateImage(file, extension, header);
|
||||||
|
}
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
catch (ServiceException e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new ServiceException("无法读取上传文件,请确认文件未损坏");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateImage(MultipartFile file, String extension, byte[] header) throws Exception
|
||||||
|
{
|
||||||
|
if ("webp".equals(extension))
|
||||||
|
{
|
||||||
|
if (!matchesAscii(header, 0, "RIFF") || !matchesAscii(header, 8, "WEBP"))
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件内容不是有效的WebP图片");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (ImageInputStream input = ImageIO.createImageInputStream(file.getInputStream()))
|
||||||
|
{
|
||||||
|
if (input == null)
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件内容不是有效图片");
|
||||||
|
}
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||||
|
if (!readers.hasNext())
|
||||||
|
{
|
||||||
|
throw new ServiceException("文件内容不是有效图片");
|
||||||
|
}
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reader.setInput(input, true, true);
|
||||||
|
long pixels = (long) reader.getWidth(0) * reader.getHeight(0);
|
||||||
|
if (pixels <= 0 || pixels > MAX_IMAGE_PIXELS)
|
||||||
|
{
|
||||||
|
throw new ServiceException("图片尺寸过大,最大支持4000万像素");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isImage(String extension)
|
||||||
|
{
|
||||||
|
return Set.of("jpg", "jpeg", "png", "bmp", "webp").contains(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean startsWith(byte[] source, int... prefix)
|
||||||
|
{
|
||||||
|
if (source.length < prefix.length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < prefix.length; i++)
|
||||||
|
{
|
||||||
|
if ((source[i] & 0xFF) != prefix[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matchesAscii(byte[] source, int offset, String expected)
|
||||||
|
{
|
||||||
|
if (source.length < offset + expected.length())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < expected.length(); i++)
|
||||||
|
{
|
||||||
|
if (source[offset + i] != (byte) expected.charAt(i))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.ruoyi.warehouse.util;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public final class WarehouseNoUtils
|
||||||
|
{
|
||||||
|
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||||
|
|
||||||
|
private WarehouseNoUtils()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String next(String prefix)
|
||||||
|
{
|
||||||
|
String random = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
|
||||||
|
return prefix + TIME_FORMATTER.format(LocalDateTime.now()) + random;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.project.mapper.ProjectBudgetMapper">
|
||||||
|
|
||||||
|
<resultMap id="ExpenseCategoryResult" type="com.ruoyi.project.domain.ExpenseCategory">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="categoryCode" column="category_code"/>
|
||||||
|
<result property="categoryName" column="category_name"/>
|
||||||
|
<result property="sortOrder" column="sort_order"/>
|
||||||
|
<result property="status" column="status"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
<result property="createBy" column="create_by"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateBy" column="update_by"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ProjectBudgetResult" type="com.ruoyi.project.domain.ProjectBudget">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="projectId" column="project_id"/>
|
||||||
|
<result property="projectCode" column="project_code"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="categoryCode" column="category_code"/>
|
||||||
|
<result property="categoryName" column="category_name"/>
|
||||||
|
<result property="budgetAmount" column="budget_amount"/>
|
||||||
|
<result property="committedAmount" column="committed_amount"/>
|
||||||
|
<result property="spentAmount" column="spent_amount"/>
|
||||||
|
<result property="availableAmount" column="available_amount"/>
|
||||||
|
<result property="alertPercent" column="alert_percent"/>
|
||||||
|
<result property="version" column="version"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="ProjectFundLedgerResult" type="com.ruoyi.project.domain.ProjectFundLedger">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="ledgerNo" column="ledger_no"/>
|
||||||
|
<result property="projectId" column="project_id"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="categoryName" column="category_name"/>
|
||||||
|
<result property="bizType" column="biz_type"/>
|
||||||
|
<result property="bizId" column="biz_id"/>
|
||||||
|
<result property="bizNo" column="biz_no"/>
|
||||||
|
<result property="budgetDelta" column="budget_delta"/>
|
||||||
|
<result property="committedDelta" column="committed_delta"/>
|
||||||
|
<result property="spentDelta" column="spent_delta"/>
|
||||||
|
<result property="balanceBudget" column="balance_budget"/>
|
||||||
|
<result property="balanceCommitted" column="balance_committed"/>
|
||||||
|
<result property="balanceSpent" column="balance_spent"/>
|
||||||
|
<result property="idempotencyKey" column="idempotency_key"/>
|
||||||
|
<result property="operator" column="operator"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectExpenseCategoryList" parameterType="com.ruoyi.project.domain.ExpenseCategory" resultMap="ExpenseCategoryResult">
|
||||||
|
select * from expense_category
|
||||||
|
<where>
|
||||||
|
<if test="categoryCode != null and categoryCode != ''">and category_code like concat('%', #{categoryCode}, '%')</if>
|
||||||
|
<if test="categoryName != null and categoryName != ''">and category_name like concat('%', #{categoryName}, '%')</if>
|
||||||
|
<if test="status != null">and status = #{status}</if>
|
||||||
|
</where>
|
||||||
|
order by sort_order, id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExpenseCategoryById" parameterType="Long" resultMap="ExpenseCategoryResult">
|
||||||
|
select * from expense_category where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectExpenseCategoryByCode" parameterType="String" resultMap="ExpenseCategoryResult">
|
||||||
|
select * from expense_category where category_code = #{categoryCode} limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertExpenseCategory" parameterType="com.ruoyi.project.domain.ExpenseCategory" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
values(#{categoryCode}, #{categoryName}, #{sortOrder}, #{status}, #{remark}, #{createBy}, sysdate())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateExpenseCategory" parameterType="com.ruoyi.project.domain.ExpenseCategory">
|
||||||
|
update expense_category set category_name = #{categoryName}, sort_order = #{sortOrder}, status = #{status},
|
||||||
|
remark = #{remark}, update_by = #{updateBy}, update_time = sysdate()
|
||||||
|
where id = #{id} and category_code != 'LEGACY'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteExpenseCategory" parameterType="Long">
|
||||||
|
delete from expense_category where id = #{id} and category_code != 'LEGACY'
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="countCategoryBusiness" parameterType="Long" resultType="int">
|
||||||
|
select (select count(*) from project_budget where expense_category_id = #{id})
|
||||||
|
+ (select count(*) from purchase_request where expense_category_id = #{id})
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectBudgetList" resultMap="ProjectBudgetResult">
|
||||||
|
select b.id, b.project_id, p.project_code, p.project_name, b.expense_category_id,
|
||||||
|
c.category_code, c.category_name, b.budget_amount, b.committed_amount, b.spent_amount,
|
||||||
|
b.budget_amount - b.committed_amount - b.spent_amount as available_amount,
|
||||||
|
b.alert_percent, b.version, b.remark
|
||||||
|
from project_budget b
|
||||||
|
join project_info p on p.id = b.project_id
|
||||||
|
join expense_category c on c.id = b.expense_category_id
|
||||||
|
where b.project_id = #{projectId}
|
||||||
|
<if test="userId != null">
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</if>
|
||||||
|
order by c.sort_order, c.id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectBudgetById" parameterType="Long" resultMap="ProjectBudgetResult">
|
||||||
|
select b.id, b.project_id, p.project_code, p.project_name, b.expense_category_id,
|
||||||
|
c.category_code, c.category_name, b.budget_amount, b.committed_amount, b.spent_amount,
|
||||||
|
b.budget_amount - b.committed_amount - b.spent_amount as available_amount,
|
||||||
|
b.alert_percent, b.version, b.remark
|
||||||
|
from project_budget b
|
||||||
|
join project_info p on p.id = b.project_id
|
||||||
|
join expense_category c on c.id = b.expense_category_id
|
||||||
|
where b.id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectBudgetForUpdate" resultMap="ProjectBudgetResult">
|
||||||
|
select b.*, b.budget_amount - b.committed_amount - b.spent_amount as available_amount
|
||||||
|
from project_budget b
|
||||||
|
where b.project_id = #{projectId} and b.expense_category_id = #{expenseCategoryId}
|
||||||
|
for update
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertProjectBudget" parameterType="com.ruoyi.project.domain.ProjectBudget" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into project_budget(project_id, expense_category_id, budget_amount, committed_amount, spent_amount,
|
||||||
|
alert_percent, version, remark, create_by, create_time)
|
||||||
|
values(#{projectId}, #{expenseCategoryId}, #{budgetAmount}, #{committedAmount}, #{spentAmount},
|
||||||
|
#{alertPercent}, 0, #{remark}, #{createBy}, sysdate())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateProjectBudgetBalance" parameterType="com.ruoyi.project.domain.ProjectBudget">
|
||||||
|
update project_budget
|
||||||
|
set budget_amount = #{budgetAmount}, committed_amount = #{committedAmount}, spent_amount = #{spentAmount},
|
||||||
|
alert_percent = #{alertPercent}, version = version + 1, remark = #{remark},
|
||||||
|
update_by = #{updateBy}, update_time = sysdate()
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectFundLedgerList" resultMap="ProjectFundLedgerResult">
|
||||||
|
select l.*, p.project_name, c.category_name
|
||||||
|
from project_fund_ledger l
|
||||||
|
join project_info p on p.id = l.project_id
|
||||||
|
join expense_category c on c.id = l.expense_category_id
|
||||||
|
<where>
|
||||||
|
<if test="ledger.projectId != null">and l.project_id = #{ledger.projectId}</if>
|
||||||
|
<if test="ledger.expenseCategoryId != null">and l.expense_category_id = #{ledger.expenseCategoryId}</if>
|
||||||
|
<if test="ledger.bizType != null and ledger.bizType != ''">and l.biz_type = #{ledger.bizType}</if>
|
||||||
|
<if test="ledger.bizNo != null and ledger.bizNo != ''">and l.biz_no like concat('%', #{ledger.bizNo}, '%')</if>
|
||||||
|
<if test="userId != null">
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by l.id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectLedgerByIdempotencyKey" parameterType="String" resultMap="ProjectFundLedgerResult">
|
||||||
|
select * from project_fund_ledger where idempotency_key = #{idempotencyKey} limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertFundLedger" parameterType="com.ruoyi.project.domain.ProjectFundLedger" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into project_fund_ledger(ledger_no, project_id, expense_category_id, biz_type, biz_id, biz_no,
|
||||||
|
budget_delta, committed_delta, spent_delta, balance_budget,
|
||||||
|
balance_committed, balance_spent, idempotency_key, operator, remark, create_time)
|
||||||
|
values(#{ledgerNo}, #{projectId}, #{expenseCategoryId}, #{bizType}, #{bizId}, #{bizNo},
|
||||||
|
#{budgetDelta}, #{committedDelta}, #{spentDelta}, #{balanceBudget},
|
||||||
|
#{balanceCommitted}, #{balanceSpent}, #{idempotencyKey}, #{operator}, #{remark}, sysdate())
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.project.mapper.ProjectInfoMapper">
|
||||||
|
|
||||||
|
<resultMap id="ProjectInfoResult" type="com.ruoyi.project.domain.ProjectInfo">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="projectCode" column="project_code"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="managerId" column="manager_id"/>
|
||||||
|
<result property="managerName" column="manager_name"/>
|
||||||
|
<result property="deptId" column="dept_id"/>
|
||||||
|
<result property="deptName" column="dept_name"/>
|
||||||
|
<result property="startDate" column="start_date"/>
|
||||||
|
<result property="endDate" column="end_date"/>
|
||||||
|
<result property="projectStatus" column="project_status"/>
|
||||||
|
<result property="currency" column="currency"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
<result property="createBy" column="create_by"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateBy" column="update_by"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
<result property="budgetAmount" column="budget_amount"/>
|
||||||
|
<result property="committedAmount" column="committed_amount"/>
|
||||||
|
<result property="spentAmount" column="spent_amount"/>
|
||||||
|
<result property="availableAmount" column="available_amount"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectProjectInfoVo">
|
||||||
|
select p.id, p.project_code, p.project_name, p.manager_id,
|
||||||
|
coalesce(u.nick_name, u.user_name) as manager_name,
|
||||||
|
p.dept_id, d.dept_name, p.start_date, p.end_date, p.project_status,
|
||||||
|
p.currency, p.remark, p.create_by, p.create_time, p.update_by, p.update_time,
|
||||||
|
coalesce(b.budget_amount, 0) as budget_amount,
|
||||||
|
coalesce(b.committed_amount, 0) as committed_amount,
|
||||||
|
coalesce(b.spent_amount, 0) as spent_amount,
|
||||||
|
coalesce(b.budget_amount, 0) - coalesce(b.committed_amount, 0) - coalesce(b.spent_amount, 0) as available_amount
|
||||||
|
from project_info p
|
||||||
|
left join sys_user u on u.user_id = p.manager_id
|
||||||
|
left join sys_dept d on d.dept_id = p.dept_id
|
||||||
|
left join (
|
||||||
|
select project_id, sum(budget_amount) budget_amount,
|
||||||
|
sum(committed_amount) committed_amount, sum(spent_amount) spent_amount
|
||||||
|
from project_budget group by project_id
|
||||||
|
) b on b.project_id = p.id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectProjectInfoList" resultMap="ProjectInfoResult">
|
||||||
|
<include refid="selectProjectInfoVo"/>
|
||||||
|
<where>
|
||||||
|
<if test="project.projectCode != null and project.projectCode != ''">
|
||||||
|
and p.project_code like concat('%', #{project.projectCode}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="project.projectName != null and project.projectName != ''">
|
||||||
|
and p.project_name like concat('%', #{project.projectName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="project.managerId != null">and p.manager_id = #{project.managerId}</if>
|
||||||
|
<if test="project.projectStatus != null">and p.project_status = #{project.projectStatus}</if>
|
||||||
|
<if test="userId != null">
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by p.id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectInfoById" resultMap="ProjectInfoResult">
|
||||||
|
<include refid="selectProjectInfoVo"/>
|
||||||
|
where p.id = #{id}
|
||||||
|
<if test="userId != null">
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectInfoByCode" parameterType="String" resultMap="ProjectInfoResult">
|
||||||
|
<include refid="selectProjectInfoVo"/> where p.project_code = #{projectCode} limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProjectOptions" resultMap="ProjectInfoResult">
|
||||||
|
<include refid="selectProjectInfoVo"/>
|
||||||
|
<where>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
and (p.project_code like concat('%', #{keyword}, '%') or p.project_name like concat('%', #{keyword}, '%'))
|
||||||
|
</if>
|
||||||
|
<if test="!includeArchived">and p.project_status in (0, 1)</if>
|
||||||
|
<if test="userId != null">
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
order by p.project_status, p.project_name limit 100
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertProjectInfo" parameterType="com.ruoyi.project.domain.ProjectInfo" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into project_info(project_code, project_name, manager_id, dept_id, start_date, end_date,
|
||||||
|
project_status, currency, remark, create_by, create_time)
|
||||||
|
values(#{projectCode}, #{projectName}, #{managerId}, #{deptId}, #{startDate}, #{endDate},
|
||||||
|
#{projectStatus}, #{currency}, #{remark}, #{createBy}, sysdate())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateProjectInfo" parameterType="com.ruoyi.project.domain.ProjectInfo">
|
||||||
|
update project_info
|
||||||
|
set project_code = #{projectCode}, project_name = #{projectName}, manager_id = #{managerId},
|
||||||
|
dept_id = #{deptId}, start_date = #{startDate}, end_date = #{endDate},
|
||||||
|
project_status = #{projectStatus}, currency = #{currency}, remark = #{remark},
|
||||||
|
update_by = #{updateBy}, update_time = sysdate()
|
||||||
|
where id = #{id} and project_code != 'LEGACY'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteProjectInfo" parameterType="Long">
|
||||||
|
delete from project_info where id = #{id} and project_code != 'LEGACY'
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="countProjectBusiness" parameterType="Long" resultType="int">
|
||||||
|
select (select count(*) from purchase_request where project_id = #{projectId})
|
||||||
|
+ (select count(*) from stock_lot where source_project_id = #{projectId})
|
||||||
|
+ (select count(*) from out_record where usage_project_id = #{projectId})
|
||||||
|
+ (select count(*) from project_budget where project_id = #{projectId})
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteProjectMembers" parameterType="Long">
|
||||||
|
delete from project_member where project_id = #{projectId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<insert id="insertProjectMember">
|
||||||
|
insert into project_member(project_id, user_id, member_role, create_by, create_time)
|
||||||
|
values(#{projectId}, #{userId}, #{memberRole}, #{createBy}, sysdate())
|
||||||
|
on duplicate key update member_role = values(member_role)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectProjectMemberIds" parameterType="Long" resultType="long">
|
||||||
|
select user_id from project_member where project_id = #{projectId} order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countProjectAccess" resultType="int">
|
||||||
|
select count(*) from project_info p
|
||||||
|
where p.id = #{projectId}
|
||||||
|
and (p.manager_id = #{userId} or exists (
|
||||||
|
select 1 from project_member pm where pm.project_id = p.id and pm.user_id = #{userId}
|
||||||
|
))
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectLegacyProjectId" resultType="long">
|
||||||
|
select id from project_info where project_code = 'LEGACY' limit 1
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.warehouse.mapper.AiCommandMapper">
|
||||||
|
<resultMap id="AiCommandResult" type="com.ruoyi.warehouse.domain.AiCommand">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="userId" column="user_id"/>
|
||||||
|
<result property="username" column="username"/>
|
||||||
|
<result property="action" column="action"/>
|
||||||
|
<result property="requiredPermission" column="required_permission"/>
|
||||||
|
<result property="requestText" column="request_text"/>
|
||||||
|
<result property="planJson" column="plan_json"/>
|
||||||
|
<result property="resultJson" column="result_json"/>
|
||||||
|
<result property="commandStatus" column="command_status"/>
|
||||||
|
<result property="expiresAt" column="expires_at"/>
|
||||||
|
<result property="confirmedAt" column="confirmed_at"/>
|
||||||
|
<result property="executedAt" column="executed_at"/>
|
||||||
|
<result property="errorMessage" column="error_message"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<insert id="insertAiCommand" parameterType="com.ruoyi.warehouse.domain.AiCommand" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into ai_command(user_id, username, action, required_permission, request_text, plan_json,
|
||||||
|
command_status, expires_at, create_time)
|
||||||
|
values(#{userId}, #{username}, #{action}, #{requiredPermission}, #{requestText}, cast(#{planJson} as json),
|
||||||
|
#{commandStatus}, #{expiresAt}, sysdate())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectAiCommandForUpdate" resultMap="AiCommandResult">
|
||||||
|
select * from ai_command where id = #{id} and user_id = #{userId} for update
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="markExecuting">
|
||||||
|
update ai_command set command_status = 'EXECUTING', confirmed_at = sysdate(), update_time = sysdate()
|
||||||
|
where id = #{id} and user_id = #{userId} and command_status = 'WAIT_CONFIRM' and expires_at > sysdate()
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="markSucceeded">
|
||||||
|
update ai_command set command_status = 'SUCCEEDED', result_json = cast(#{resultJson} as json),
|
||||||
|
executed_at = sysdate(), update_time = sysdate()
|
||||||
|
where id = #{id} and command_status = 'EXECUTING'
|
||||||
|
</update>
|
||||||
|
</mapper>
|
||||||
@ -8,12 +8,17 @@
|
|||||||
<result property="productId" column="product_id"/>
|
<result property="productId" column="product_id"/>
|
||||||
<result property="purchaseRequestId" column="purchase_request_id"/>
|
<result property="purchaseRequestId" column="purchase_request_id"/>
|
||||||
<result property="arrivalId" column="arrival_id"/>
|
<result property="arrivalId" column="arrival_id"/>
|
||||||
|
<result property="sourceProjectId" column="source_project_id"/>
|
||||||
|
<result property="sourceProjectName" column="source_project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="expenseCategoryName" column="expense_category_name"/>
|
||||||
<result property="sourceNo" column="source_no"/>
|
<result property="sourceNo" column="source_no"/>
|
||||||
<result property="inType" column="in_type"/>
|
<result property="inType" column="in_type"/>
|
||||||
<result property="inNum" column="in_num"/>
|
<result property="inNum" column="in_num"/>
|
||||||
<result property="inStatus" column="in_status"/>
|
<result property="inStatus" column="in_status"/>
|
||||||
<result property="operator" column="operator"/>
|
<result property="operator" column="operator"/>
|
||||||
<result property="inOperator" column="in_operator"/>
|
<result property="inOperator" column="in_operator"/>
|
||||||
|
<result property="inOperatorName" column="in_operator_name"/>
|
||||||
<result property="confirmTime" column="confirm_time"/>
|
<result property="confirmTime" column="confirm_time"/>
|
||||||
<result property="remark" column="remark"/>
|
<result property="remark" column="remark"/>
|
||||||
<result property="createTime" column="create_time"/>
|
<result property="createTime" column="create_time"/>
|
||||||
@ -29,15 +34,20 @@
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectInRecordVo">
|
<sql id="selectInRecordVo">
|
||||||
select ir.id, ir.in_no, ir.area_id, ir.product_id, ir.purchase_request_id, ir.arrival_id, ir.source_no,
|
select ir.id, ir.in_no, ir.area_id, ir.product_id, ir.purchase_request_id, ir.arrival_id,
|
||||||
|
ir.source_project_id, project.project_name as source_project_name,
|
||||||
|
ir.expense_category_id, ec.category_name as expense_category_name, ir.source_no,
|
||||||
ir.in_type, ir.in_num, ir.in_status,
|
ir.in_type, ir.in_num, ir.in_status,
|
||||||
coalesce(operator_user.nick_name, ir.operator) as operator,
|
coalesce(operator_user.nick_name, ir.operator) as operator,
|
||||||
coalesce(in_user.nick_name, ir.in_operator) as in_operator,
|
ir.in_operator,
|
||||||
|
coalesce(in_user.nick_name, ir.in_operator) as in_operator_name,
|
||||||
ir.confirm_time, ir.remark, ir.create_time, ir.update_time,
|
ir.confirm_time, ir.remark, ir.create_time, ir.update_time,
|
||||||
wa.area_code, wa.area_name, pi.product_code, pi.product_name, pi.brand, pi.category, pi.spec, pi.unit
|
wa.area_code, wa.area_name, pi.product_code, pi.product_name, pi.brand, pi.category, pi.spec, pi.unit
|
||||||
from in_record ir
|
from in_record ir
|
||||||
left join warehouse_area wa on wa.id = ir.area_id
|
left join warehouse_area wa on wa.id = ir.area_id
|
||||||
left join product_info pi on pi.id = ir.product_id
|
left join product_info pi on pi.id = ir.product_id
|
||||||
|
left join project_info project on project.id = ir.source_project_id
|
||||||
|
left join expense_category ec on ec.id = ir.expense_category_id
|
||||||
left join sys_user operator_user on operator_user.user_name = ir.operator
|
left join sys_user operator_user on operator_user.user_name = ir.operator
|
||||||
left join sys_user in_user on in_user.user_name = ir.in_operator
|
left join sys_user in_user on in_user.user_name = ir.in_operator
|
||||||
</sql>
|
</sql>
|
||||||
@ -48,6 +58,7 @@
|
|||||||
<if test="inNo != null and inNo != ''">and ir.in_no like concat('%', #{inNo}, '%')</if>
|
<if test="inNo != null and inNo != ''">and ir.in_no like concat('%', #{inNo}, '%')</if>
|
||||||
<if test="areaId != null">and ir.area_id = #{areaId}</if>
|
<if test="areaId != null">and ir.area_id = #{areaId}</if>
|
||||||
<if test="productId != null">and ir.product_id = #{productId}</if>
|
<if test="productId != null">and ir.product_id = #{productId}</if>
|
||||||
|
<if test="sourceProjectId != null">and ir.source_project_id = #{sourceProjectId}</if>
|
||||||
<if test="areaName != null and areaName != ''">and wa.area_name like concat('%', #{areaName}, '%')</if>
|
<if test="areaName != null and areaName != ''">and wa.area_name like concat('%', #{areaName}, '%')</if>
|
||||||
<if test="productName != null and productName != ''">and pi.product_name like concat('%', #{productName}, '%')</if>
|
<if test="productName != null and productName != ''">and pi.product_name like concat('%', #{productName}, '%')</if>
|
||||||
<if test="brand != null and brand != ''">and pi.brand like concat('%', #{brand}, '%')</if>
|
<if test="brand != null and brand != ''">and pi.brand like concat('%', #{brand}, '%')</if>
|
||||||
@ -56,7 +67,8 @@
|
|||||||
<if test="inType != null">and ir.in_type = #{inType}</if>
|
<if test="inType != null">and ir.in_type = #{inType}</if>
|
||||||
<if test="inStatus != null">and ir.in_status = #{inStatus}</if>
|
<if test="inStatus != null">and ir.in_status = #{inStatus}</if>
|
||||||
<if test="inOperator != null and inOperator != ''">
|
<if test="inOperator != null and inOperator != ''">
|
||||||
and coalesce(in_user.nick_name, ir.in_operator) like concat('%', #{inOperator}, '%')
|
and (ir.in_operator like concat('%', #{inOperator}, '%')
|
||||||
|
or in_user.nick_name like concat('%', #{inOperator}, '%'))
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
order by ir.id desc
|
order by ir.id desc
|
||||||
@ -67,13 +79,15 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectInRecordByIdForUpdate" parameterType="Long" resultMap="InRecordResult">
|
<select id="selectInRecordByIdForUpdate" parameterType="Long" resultMap="InRecordResult">
|
||||||
select id, in_no, area_id, product_id, purchase_request_id, arrival_id, source_no, in_type, in_num,
|
select id, in_no, area_id, product_id, purchase_request_id, arrival_id,
|
||||||
|
source_project_id, expense_category_id, source_no, in_type, in_num,
|
||||||
in_status, operator, in_operator, confirm_time, remark, create_time, update_time
|
in_status, operator, in_operator, confirm_time, remark, create_time, update_time
|
||||||
from in_record where id = #{id} for update
|
from in_record where id = #{id} for update
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectInRecordByArrivalIdForUpdate" parameterType="Long" resultMap="InRecordResult">
|
<select id="selectInRecordByArrivalIdForUpdate" parameterType="Long" resultMap="InRecordResult">
|
||||||
select id, in_no, area_id, product_id, purchase_request_id, arrival_id, source_no, in_type, in_num,
|
select id, in_no, area_id, product_id, purchase_request_id, arrival_id,
|
||||||
|
source_project_id, expense_category_id, source_no, in_type, in_num,
|
||||||
in_status, operator, in_operator, confirm_time, remark, create_time, update_time
|
in_status, operator, in_operator, confirm_time, remark, create_time, update_time
|
||||||
from in_record where arrival_id = #{arrivalId} for update
|
from in_record where arrival_id = #{arrivalId} for update
|
||||||
</select>
|
</select>
|
||||||
@ -83,9 +97,11 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertInRecord" parameterType="InRecord" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertInRecord" parameterType="InRecord" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into in_record(in_no, area_id, product_id, purchase_request_id, arrival_id, source_no, in_type, in_num,
|
insert into in_record(in_no, area_id, product_id, purchase_request_id, arrival_id,
|
||||||
|
source_project_id, expense_category_id, source_no, in_type, in_num,
|
||||||
in_status, operator, in_operator, remark, create_time)
|
in_status, operator, in_operator, remark, create_time)
|
||||||
values(#{inNo}, #{areaId}, #{productId}, #{purchaseRequestId}, #{arrivalId}, #{sourceNo}, #{inType}, #{inNum},
|
values(#{inNo}, #{areaId}, #{productId}, #{purchaseRequestId}, #{arrivalId},
|
||||||
|
#{sourceProjectId}, #{expenseCategoryId}, #{sourceNo}, #{inType}, #{inNum},
|
||||||
#{inStatus}, #{operator}, #{inOperator}, #{remark}, sysdate())
|
#{inStatus}, #{operator}, #{inOperator}, #{remark}, sysdate())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@ -95,6 +111,8 @@
|
|||||||
<if test="inNo != null and inNo != ''">in_no = #{inNo},</if>
|
<if test="inNo != null and inNo != ''">in_no = #{inNo},</if>
|
||||||
<if test="areaId != null">area_id = #{areaId},</if>
|
<if test="areaId != null">area_id = #{areaId},</if>
|
||||||
<if test="productId != null">product_id = #{productId},</if>
|
<if test="productId != null">product_id = #{productId},</if>
|
||||||
|
<if test="sourceProjectId != null">source_project_id = #{sourceProjectId},</if>
|
||||||
|
<if test="expenseCategoryId != null">expense_category_id = #{expenseCategoryId},</if>
|
||||||
<if test="inType != null">in_type = #{inType},</if>
|
<if test="inType != null">in_type = #{inType},</if>
|
||||||
<if test="inNum != null">in_num = #{inNum},</if>
|
<if test="inNum != null">in_num = #{inNum},</if>
|
||||||
<if test="operator != null">operator = #{operator},</if>
|
<if test="operator != null">operator = #{operator},</if>
|
||||||
|
|||||||
@ -6,11 +6,17 @@
|
|||||||
<result property="outNo" column="out_no"/>
|
<result property="outNo" column="out_no"/>
|
||||||
<result property="areaId" column="area_id"/>
|
<result property="areaId" column="area_id"/>
|
||||||
<result property="productId" column="product_id"/>
|
<result property="productId" column="product_id"/>
|
||||||
|
<result property="usageProjectId" column="usage_project_id"/>
|
||||||
|
<result property="usageProjectName" column="usage_project_name"/>
|
||||||
<result property="outType" column="out_type"/>
|
<result property="outType" column="out_type"/>
|
||||||
<result property="outNum" column="out_num"/>
|
<result property="outNum" column="out_num"/>
|
||||||
<result property="sourceNo" column="source_no"/>
|
<result property="sourceNo" column="source_no"/>
|
||||||
|
<result property="crossProjectFlag" column="cross_project_flag"/>
|
||||||
|
<result property="crossProjectReason" column="cross_project_reason"/>
|
||||||
|
<result property="sourceProjectNames" column="source_project_names"/>
|
||||||
<result property="operator" column="operator"/>
|
<result property="operator" column="operator"/>
|
||||||
<result property="outOperator" column="out_operator"/>
|
<result property="outOperator" column="out_operator"/>
|
||||||
|
<result property="outOperatorName" column="out_operator_name"/>
|
||||||
<result property="outTime" column="out_time"/>
|
<result property="outTime" column="out_time"/>
|
||||||
<result property="remark" column="remark"/>
|
<result property="remark" column="remark"/>
|
||||||
<result property="createTime" column="create_time"/>
|
<result property="createTime" column="create_time"/>
|
||||||
@ -26,15 +32,24 @@
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectOutRecordVo">
|
<sql id="selectOutRecordVo">
|
||||||
select orr.id, orr.out_no, orr.area_id, orr.product_id, orr.out_type, orr.out_num,
|
select orr.id, orr.out_no, orr.area_id, orr.product_id, orr.usage_project_id,
|
||||||
orr.source_no,
|
usage_project.project_name as usage_project_name,
|
||||||
|
orr.out_type, orr.out_num, orr.source_no, orr.cross_project_flag, orr.cross_project_reason,
|
||||||
|
lot_project.source_project_names,
|
||||||
coalesce(operator_user.nick_name, orr.operator) as operator,
|
coalesce(operator_user.nick_name, orr.operator) as operator,
|
||||||
coalesce(out_user.nick_name, orr.out_operator) as out_operator,
|
orr.out_operator,
|
||||||
|
coalesce(out_user.nick_name, orr.out_operator) as out_operator_name,
|
||||||
orr.out_time, orr.remark, orr.create_time, orr.update_time,
|
orr.out_time, orr.remark, orr.create_time, orr.update_time,
|
||||||
wa.area_code, wa.area_name, pi.product_code, pi.product_name, pi.brand, pi.category, pi.spec, pi.unit
|
wa.area_code, wa.area_name, pi.product_code, pi.product_name, pi.brand, pi.category, pi.spec, pi.unit
|
||||||
from out_record orr
|
from out_record orr
|
||||||
left join warehouse_area wa on wa.id = orr.area_id
|
left join warehouse_area wa on wa.id = orr.area_id
|
||||||
left join product_info pi on pi.id = orr.product_id
|
left join product_info pi on pi.id = orr.product_id
|
||||||
|
left join project_info usage_project on usage_project.id = orr.usage_project_id
|
||||||
|
left join (
|
||||||
|
select orl.out_record_id, group_concat(distinct p.project_name order by p.project_name separator '、') as source_project_names
|
||||||
|
from out_record_lot orl join project_info p on p.id = orl.source_project_id
|
||||||
|
group by orl.out_record_id
|
||||||
|
) lot_project on lot_project.out_record_id = orr.id
|
||||||
left join sys_user operator_user on operator_user.user_name = orr.operator
|
left join sys_user operator_user on operator_user.user_name = orr.operator
|
||||||
left join sys_user out_user on out_user.user_name = orr.out_operator
|
left join sys_user out_user on out_user.user_name = orr.out_operator
|
||||||
</sql>
|
</sql>
|
||||||
@ -45,6 +60,8 @@
|
|||||||
<if test="outNo != null and outNo != ''">and orr.out_no like concat('%', #{outNo}, '%')</if>
|
<if test="outNo != null and outNo != ''">and orr.out_no like concat('%', #{outNo}, '%')</if>
|
||||||
<if test="areaId != null">and orr.area_id = #{areaId}</if>
|
<if test="areaId != null">and orr.area_id = #{areaId}</if>
|
||||||
<if test="productId != null">and orr.product_id = #{productId}</if>
|
<if test="productId != null">and orr.product_id = #{productId}</if>
|
||||||
|
<if test="usageProjectId != null">and orr.usage_project_id = #{usageProjectId}</if>
|
||||||
|
<if test="crossProjectFlag != null">and orr.cross_project_flag = #{crossProjectFlag}</if>
|
||||||
<if test="areaName != null and areaName != ''">and wa.area_name like concat('%', #{areaName}, '%')</if>
|
<if test="areaName != null and areaName != ''">and wa.area_name like concat('%', #{areaName}, '%')</if>
|
||||||
<if test="productName != null and productName != ''">and pi.product_name like concat('%', #{productName}, '%')</if>
|
<if test="productName != null and productName != ''">and pi.product_name like concat('%', #{productName}, '%')</if>
|
||||||
<if test="brand != null and brand != ''">and pi.brand like concat('%', #{brand}, '%')</if>
|
<if test="brand != null and brand != ''">and pi.brand like concat('%', #{brand}, '%')</if>
|
||||||
@ -52,7 +69,8 @@
|
|||||||
<if test="spec != null and spec != ''">and pi.spec like concat('%', #{spec}, '%')</if>
|
<if test="spec != null and spec != ''">and pi.spec like concat('%', #{spec}, '%')</if>
|
||||||
<if test="outType != null">and orr.out_type = #{outType}</if>
|
<if test="outType != null">and orr.out_type = #{outType}</if>
|
||||||
<if test="outOperator != null and outOperator != ''">
|
<if test="outOperator != null and outOperator != ''">
|
||||||
and coalesce(out_user.nick_name, orr.out_operator) like concat('%', #{outOperator}, '%')
|
and (orr.out_operator like concat('%', #{outOperator}, '%')
|
||||||
|
or out_user.nick_name like concat('%', #{outOperator}, '%'))
|
||||||
</if>
|
</if>
|
||||||
<if test="sourceNo != null and sourceNo != ''">and orr.source_no like concat('%', #{sourceNo}, '%')</if>
|
<if test="sourceNo != null and sourceNo != ''">and orr.source_no like concat('%', #{sourceNo}, '%')</if>
|
||||||
</where>
|
</where>
|
||||||
@ -68,7 +86,15 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertOutRecord" parameterType="OutRecord" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertOutRecord" parameterType="OutRecord" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into out_record(out_no, area_id, product_id, out_type, out_num, source_no, operator, out_operator, out_time, remark, create_time)
|
insert into out_record(out_no, area_id, product_id, usage_project_id, out_type, out_num, source_no,
|
||||||
values(#{outNo}, #{areaId}, #{productId}, #{outType}, #{outNum}, #{sourceNo}, #{operator}, #{outOperator}, #{outTime}, #{remark}, sysdate())
|
cross_project_flag, cross_project_reason, operator, out_operator, out_time, remark, create_time)
|
||||||
|
values(#{outNo}, #{areaId}, #{productId}, #{usageProjectId}, #{outType}, #{outNum}, #{sourceNo},
|
||||||
|
#{crossProjectFlag}, #{crossProjectReason}, #{operator}, #{outOperator}, #{outTime}, #{remark}, sysdate())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateOutRecordProjectFlags" parameterType="OutRecord">
|
||||||
|
update out_record set cross_project_flag = #{crossProjectFlag},
|
||||||
|
cross_project_reason = #{crossProjectReason}, update_time = sysdate()
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
<result property="arrivalNo" column="arrival_no"/>
|
<result property="arrivalNo" column="arrival_no"/>
|
||||||
<result property="purchaseRequestId" column="purchase_request_id"/>
|
<result property="purchaseRequestId" column="purchase_request_id"/>
|
||||||
<result property="arrivalQuantity" column="arrival_quantity"/>
|
<result property="arrivalQuantity" column="arrival_quantity"/>
|
||||||
|
<result property="actualUnitPrice" column="actual_unit_price"/>
|
||||||
|
<result property="actualAmount" column="actual_amount"/>
|
||||||
<result property="arrivalDate" column="arrival_date"/>
|
<result property="arrivalDate" column="arrival_date"/>
|
||||||
<result property="acceptanceStatus" column="acceptance_status"/>
|
<result property="acceptanceStatus" column="acceptance_status"/>
|
||||||
<result property="acceptanceRemark" column="acceptance_remark"/>
|
<result property="acceptanceRemark" column="acceptance_remark"/>
|
||||||
@ -29,10 +31,16 @@
|
|||||||
<result property="materialBrand" column="material_brand"/>
|
<result property="materialBrand" column="material_brand"/>
|
||||||
<result property="materialParams" column="material_params"/>
|
<result property="materialParams" column="material_params"/>
|
||||||
<result property="unit" column="unit"/>
|
<result property="unit" column="unit"/>
|
||||||
|
<result property="projectId" column="project_id"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="expenseCategoryName" column="expense_category_name"/>
|
||||||
|
<result property="estimatedUnitPrice" column="estimated_unit_price"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectPurchaseArrivalVo">
|
<sql id="selectPurchaseArrivalVo">
|
||||||
select pa.id, pa.arrival_no, pa.purchase_request_id, pa.arrival_quantity, pa.arrival_date,
|
select pa.id, pa.arrival_no, pa.purchase_request_id, pa.arrival_quantity,
|
||||||
|
pa.actual_unit_price, pa.actual_amount, pa.arrival_date,
|
||||||
pa.acceptance_status, pa.acceptance_remark,
|
pa.acceptance_status, pa.acceptance_remark,
|
||||||
coalesce(accepted_user.nick_name, pa.accepted_by) as accepted_by, pa.accepted_time,
|
coalesce(accepted_user.nick_name, pa.accepted_by) as accepted_by, pa.accepted_time,
|
||||||
pa.pre_accept_purchase_status,
|
pa.pre_accept_purchase_status,
|
||||||
@ -42,10 +50,14 @@
|
|||||||
coalesce(updated_user.nick_name, pa.update_by) as update_by, pa.update_time,
|
coalesce(updated_user.nick_name, pa.update_by) as update_by, pa.update_time,
|
||||||
pr.request_no, pr.applicant_id,
|
pr.request_no, pr.applicant_id,
|
||||||
coalesce(applicant_user.nick_name, pr.applicant_name) as applicant_name, pr.product_id,
|
coalesce(applicant_user.nick_name, pr.applicant_name) as applicant_name, pr.product_id,
|
||||||
pi.product_code, pr.material_name, pr.material_brand, pr.material_params, pr.unit
|
pi.product_code, pr.material_name, pr.material_brand, pr.material_params, pr.unit,
|
||||||
|
pr.project_id, project.project_name, pr.expense_category_id,
|
||||||
|
ec.category_name as expense_category_name, pr.estimated_unit_price
|
||||||
from purchase_arrival pa
|
from purchase_arrival pa
|
||||||
join purchase_request pr on pr.id = pa.purchase_request_id
|
join purchase_request pr on pr.id = pa.purchase_request_id
|
||||||
left join product_info pi on pi.id = pr.product_id
|
left join product_info pi on pi.id = pr.product_id
|
||||||
|
left join project_info project on project.id = pr.project_id
|
||||||
|
left join expense_category ec on ec.id = pr.expense_category_id
|
||||||
left join sys_user applicant_user on applicant_user.user_id = pr.applicant_id
|
left join sys_user applicant_user on applicant_user.user_id = pr.applicant_id
|
||||||
left join sys_user accepted_user on accepted_user.user_name = pa.accepted_by collate utf8mb4_unicode_ci
|
left join sys_user accepted_user on accepted_user.user_name = pa.accepted_by collate utf8mb4_unicode_ci
|
||||||
left join sys_user revoked_user on revoked_user.user_name = pa.revoked_by collate utf8mb4_unicode_ci
|
left join sys_user revoked_user on revoked_user.user_name = pa.revoked_by collate utf8mb4_unicode_ci
|
||||||
@ -64,12 +76,14 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectPurchaseArrivalByIdForUpdate" parameterType="Long" resultMap="PurchaseArrivalResult">
|
<select id="selectPurchaseArrivalByIdForUpdate" parameterType="Long" resultMap="PurchaseArrivalResult">
|
||||||
select pa.id, pa.arrival_no, pa.purchase_request_id, pa.arrival_quantity, pa.arrival_date,
|
select pa.id, pa.arrival_no, pa.purchase_request_id, pa.arrival_quantity,
|
||||||
|
pa.actual_unit_price, pa.actual_amount, pa.arrival_date,
|
||||||
pa.acceptance_status, pa.acceptance_remark, pa.accepted_by, pa.accepted_time,
|
pa.acceptance_status, pa.acceptance_remark, pa.accepted_by, pa.accepted_time,
|
||||||
pa.pre_accept_purchase_status, pa.revoked_by, pa.revoked_time, pa.revoke_remark,
|
pa.pre_accept_purchase_status, pa.revoked_by, pa.revoked_time, pa.revoke_remark,
|
||||||
pa.remark, pa.create_by, pa.create_time, pa.update_by, pa.update_time,
|
pa.remark, pa.create_by, pa.create_time, pa.update_by, pa.update_time,
|
||||||
pr.request_no, pr.applicant_id, pr.applicant_name, pr.product_id,
|
pr.request_no, pr.applicant_id, pr.applicant_name, pr.product_id,
|
||||||
pi.product_code, pr.material_name, pr.material_brand, pr.material_params, pr.unit
|
pi.product_code, pr.material_name, pr.material_brand, pr.material_params, pr.unit,
|
||||||
|
pr.project_id, pr.expense_category_id, pr.estimated_unit_price
|
||||||
from purchase_arrival pa
|
from purchase_arrival pa
|
||||||
join purchase_request pr on pr.id = pa.purchase_request_id
|
join purchase_request pr on pr.id = pa.purchase_request_id
|
||||||
left join product_info pi on pi.id = pr.product_id
|
left join product_info pi on pi.id = pr.product_id
|
||||||
@ -93,9 +107,11 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertPurchaseArrival" parameterType="PurchaseArrival" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertPurchaseArrival" parameterType="PurchaseArrival" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into purchase_arrival(arrival_no, purchase_request_id, arrival_quantity, arrival_date,
|
insert into purchase_arrival(arrival_no, purchase_request_id, arrival_quantity,
|
||||||
|
actual_unit_price, actual_amount, arrival_date,
|
||||||
acceptance_status, remark, create_by, create_time)
|
acceptance_status, remark, create_by, create_time)
|
||||||
values(#{arrivalNo}, #{purchaseRequestId}, #{arrivalQuantity}, #{arrivalDate}, 0,
|
values(#{arrivalNo}, #{purchaseRequestId}, #{arrivalQuantity},
|
||||||
|
#{actualUnitPrice}, #{actualAmount}, #{arrivalDate}, 0,
|
||||||
#{remark}, #{createBy}, sysdate())
|
#{remark}, #{createBy}, sysdate())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,20 @@
|
|||||||
<result property="requestNo" column="request_no"/>
|
<result property="requestNo" column="request_no"/>
|
||||||
<result property="productId" column="product_id"/>
|
<result property="productId" column="product_id"/>
|
||||||
<result property="purchaseBatchId" column="purchase_batch_id"/>
|
<result property="purchaseBatchId" column="purchase_batch_id"/>
|
||||||
|
<result property="projectId" column="project_id"/>
|
||||||
|
<result property="projectCode" column="project_code"/>
|
||||||
|
<result property="projectName" column="project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="expenseCategoryCode" column="expense_category_code"/>
|
||||||
|
<result property="expenseCategoryName" column="expense_category_name"/>
|
||||||
<result property="materialName" column="material_name"/>
|
<result property="materialName" column="material_name"/>
|
||||||
<result property="materialBrand" column="material_brand"/>
|
<result property="materialBrand" column="material_brand"/>
|
||||||
<result property="category" column="category"/>
|
<result property="category" column="category"/>
|
||||||
<result property="materialParams" column="material_params"/>
|
<result property="materialParams" column="material_params"/>
|
||||||
<result property="purchaseLink" column="purchase_link"/>
|
<result property="purchaseLink" column="purchase_link"/>
|
||||||
<result property="quantity" column="quantity"/>
|
<result property="quantity" column="quantity"/>
|
||||||
|
<result property="estimatedUnitPrice" column="estimated_unit_price"/>
|
||||||
|
<result property="estimatedAmount" column="estimated_amount"/>
|
||||||
<result property="unit" column="unit"/>
|
<result property="unit" column="unit"/>
|
||||||
<result property="expectedDate" column="expected_date"/>
|
<result property="expectedDate" column="expected_date"/>
|
||||||
<result property="applicantId" column="applicant_id"/>
|
<result property="applicantId" column="applicant_id"/>
|
||||||
@ -37,8 +45,13 @@
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectPurchaseRequestVo">
|
<sql id="selectPurchaseRequestVo">
|
||||||
select pr.id, pr.request_no, pr.product_id, pr.purchase_batch_id, pr.material_name, pr.material_brand, pr.category, pr.material_params,
|
select pr.id, pr.request_no, pr.product_id, pr.purchase_batch_id,
|
||||||
pr.purchase_link, pr.quantity, pr.unit, pr.expected_date, pr.applicant_id,
|
pr.project_id, project.project_code, project.project_name,
|
||||||
|
pr.expense_category_id, ec.category_code as expense_category_code,
|
||||||
|
ec.category_name as expense_category_name,
|
||||||
|
pr.material_name, pr.material_brand, pr.category, pr.material_params,
|
||||||
|
pr.purchase_link, pr.quantity, pr.estimated_unit_price, pr.estimated_amount,
|
||||||
|
pr.unit, pr.expected_date, pr.applicant_id,
|
||||||
coalesce(applicant_user.nick_name, pr.applicant_name) as applicant_name,
|
coalesce(applicant_user.nick_name, pr.applicant_name) as applicant_name,
|
||||||
pr.purchase_status, pr.purchase_batch_no, pr.progress_remark, pr.remark,
|
pr.purchase_status, pr.purchase_batch_no, pr.progress_remark, pr.remark,
|
||||||
pr.create_by, pr.create_time, pr.update_by, pr.update_time,
|
pr.create_by, pr.create_time, pr.update_by, pr.update_time,
|
||||||
@ -49,12 +62,15 @@
|
|||||||
greatest(pr.quantity - coalesce(pa.accepted_quantity, 0) - coalesce(pa.pending_quantity, 0), 0) as remaining_quantity
|
greatest(pr.quantity - coalesce(pa.accepted_quantity, 0) - coalesce(pa.pending_quantity, 0), 0) as remaining_quantity
|
||||||
from purchase_request pr
|
from purchase_request pr
|
||||||
left join product_info pi on pi.id = pr.product_id
|
left join product_info pi on pi.id = pr.product_id
|
||||||
|
left join project_info project on project.id = pr.project_id
|
||||||
|
left join expense_category ec on ec.id = pr.expense_category_id
|
||||||
left join sys_user applicant_user on applicant_user.user_id = pr.applicant_id
|
left join sys_user applicant_user on applicant_user.user_id = pr.applicant_id
|
||||||
left join (
|
left join (
|
||||||
select purchase_request_id,
|
select purchase_request_id,
|
||||||
sum(case when acceptance_status = 1 then arrival_quantity else 0 end) as accepted_quantity,
|
sum(case when acceptance_status = 1 then arrival_quantity else 0 end) as accepted_quantity,
|
||||||
sum(case when acceptance_status = 0 then arrival_quantity else 0 end) as pending_quantity
|
sum(case when acceptance_status = 0 then arrival_quantity else 0 end) as pending_quantity
|
||||||
from purchase_arrival
|
from purchase_arrival
|
||||||
|
where acceptance_status in (0, 1)
|
||||||
group by purchase_request_id
|
group by purchase_request_id
|
||||||
) pa on pa.purchase_request_id = pr.id
|
) pa on pa.purchase_request_id = pr.id
|
||||||
</sql>
|
</sql>
|
||||||
@ -64,9 +80,11 @@
|
|||||||
<where>
|
<where>
|
||||||
<if test="requestNo != null and requestNo != ''">and pr.request_no like concat('%', #{requestNo}, '%')</if>
|
<if test="requestNo != null and requestNo != ''">and pr.request_no like concat('%', #{requestNo}, '%')</if>
|
||||||
<if test="productId != null">and pr.product_id = #{productId}</if>
|
<if test="productId != null">and pr.product_id = #{productId}</if>
|
||||||
|
<if test="projectId != null">and pr.project_id = #{projectId}</if>
|
||||||
|
<if test="expenseCategoryId != null">and pr.expense_category_id = #{expenseCategoryId}</if>
|
||||||
<if test="materialName != null and materialName != ''">
|
<if test="materialName != null and materialName != ''">
|
||||||
and lower(concat_ws(' ', pr.material_name, pr.material_brand, pr.category, pr.material_params, pi.product_name, pi.product_code))
|
and convert(lower(concat_ws(' ', pr.material_name, pr.material_brand, pr.category, pr.material_params, pi.product_name, pi.product_code)) using utf8mb4) collate utf8mb4_bin
|
||||||
like concat('%', lower(#{materialName}), '%')
|
like concat('%', convert(lower(#{materialName}) using utf8mb4) collate utf8mb4_bin, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="category != null and category != ''">and pr.category like concat('%', #{category}, '%')</if>
|
<if test="category != null and category != ''">and pr.category like concat('%', #{category}, '%')</if>
|
||||||
<if test="applicantId != null">and pr.applicant_id = #{applicantId}</if>
|
<if test="applicantId != null">and pr.applicant_id = #{applicantId}</if>
|
||||||
@ -87,9 +105,11 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectPurchaseRequestByIdForUpdate" parameterType="Long" resultMap="PurchaseRequestResult">
|
<select id="selectPurchaseRequestByIdForUpdate" parameterType="Long" resultMap="PurchaseRequestResult">
|
||||||
select pr.id, pr.request_no, pr.product_id, pr.purchase_batch_id, pr.material_name,
|
select pr.id, pr.request_no, pr.product_id, pr.purchase_batch_id, pr.project_id,
|
||||||
|
pr.expense_category_id, pr.material_name,
|
||||||
pr.material_brand, pr.category, pr.material_params, pr.purchase_link, pr.quantity,
|
pr.material_brand, pr.category, pr.material_params, pr.purchase_link, pr.quantity,
|
||||||
pr.unit, pr.expected_date, pr.applicant_id, pr.applicant_name, pr.purchase_status,
|
pr.estimated_unit_price, pr.estimated_amount, pr.unit, pr.expected_date,
|
||||||
|
pr.applicant_id, pr.applicant_name, pr.purchase_status,
|
||||||
pr.purchase_batch_no, pr.progress_remark, pr.remark, pr.create_by, pr.create_time,
|
pr.purchase_batch_no, pr.progress_remark, pr.remark, pr.create_by, pr.create_time,
|
||||||
pr.update_by, pr.update_time
|
pr.update_by, pr.update_time
|
||||||
from purchase_request pr where pr.id = #{id} for update
|
from purchase_request pr where pr.id = #{id} for update
|
||||||
@ -101,12 +121,16 @@
|
|||||||
|
|
||||||
<insert id="insertPurchaseRequest" parameterType="PurchaseRequest" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertPurchaseRequest" parameterType="PurchaseRequest" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into purchase_request(
|
insert into purchase_request(
|
||||||
request_no, product_id, material_name, material_brand, category, material_params, purchase_link,
|
request_no, product_id, project_id, expense_category_id,
|
||||||
quantity, unit, expected_date, applicant_id, applicant_name, purchase_status, purchase_batch_no,
|
material_name, material_brand, category, material_params, purchase_link,
|
||||||
|
quantity, estimated_unit_price, estimated_amount, unit, expected_date,
|
||||||
|
applicant_id, applicant_name, purchase_status, purchase_batch_no,
|
||||||
progress_remark, remark, create_by, create_time
|
progress_remark, remark, create_by, create_time
|
||||||
) values (
|
) values (
|
||||||
#{requestNo}, #{productId}, #{materialName}, #{materialBrand}, #{category}, #{materialParams}, #{purchaseLink},
|
#{requestNo}, #{productId}, #{projectId}, #{expenseCategoryId},
|
||||||
#{quantity}, #{unit}, #{expectedDate}, #{applicantId}, #{applicantName}, #{purchaseStatus}, #{purchaseBatchNo},
|
#{materialName}, #{materialBrand}, #{category}, #{materialParams}, #{purchaseLink},
|
||||||
|
#{quantity}, #{estimatedUnitPrice}, #{estimatedAmount}, #{unit}, #{expectedDate},
|
||||||
|
#{applicantId}, #{applicantName}, #{purchaseStatus}, #{purchaseBatchNo},
|
||||||
#{progressRemark}, #{remark}, #{createBy}, sysdate()
|
#{progressRemark}, #{remark}, #{createBy}, sysdate()
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
@ -116,12 +140,16 @@
|
|||||||
<set>
|
<set>
|
||||||
<if test="purchaseRequest.requestNo != null and purchaseRequest.requestNo != ''">request_no = #{purchaseRequest.requestNo},</if>
|
<if test="purchaseRequest.requestNo != null and purchaseRequest.requestNo != ''">request_no = #{purchaseRequest.requestNo},</if>
|
||||||
product_id = #{purchaseRequest.productId},
|
product_id = #{purchaseRequest.productId},
|
||||||
|
project_id = #{purchaseRequest.projectId},
|
||||||
|
expense_category_id = #{purchaseRequest.expenseCategoryId},
|
||||||
<if test="purchaseRequest.materialName != null and purchaseRequest.materialName != ''">material_name = #{purchaseRequest.materialName},</if>
|
<if test="purchaseRequest.materialName != null and purchaseRequest.materialName != ''">material_name = #{purchaseRequest.materialName},</if>
|
||||||
<if test="purchaseRequest.materialBrand != null">material_brand = #{purchaseRequest.materialBrand},</if>
|
<if test="purchaseRequest.materialBrand != null">material_brand = #{purchaseRequest.materialBrand},</if>
|
||||||
<if test="purchaseRequest.category != null">category = #{purchaseRequest.category},</if>
|
<if test="purchaseRequest.category != null">category = #{purchaseRequest.category},</if>
|
||||||
<if test="purchaseRequest.materialParams != null">material_params = #{purchaseRequest.materialParams},</if>
|
<if test="purchaseRequest.materialParams != null">material_params = #{purchaseRequest.materialParams},</if>
|
||||||
<if test="purchaseRequest.purchaseLink != null">purchase_link = #{purchaseRequest.purchaseLink},</if>
|
<if test="purchaseRequest.purchaseLink != null">purchase_link = #{purchaseRequest.purchaseLink},</if>
|
||||||
<if test="purchaseRequest.quantity != null">quantity = #{purchaseRequest.quantity},</if>
|
<if test="purchaseRequest.quantity != null">quantity = #{purchaseRequest.quantity},</if>
|
||||||
|
estimated_unit_price = #{purchaseRequest.estimatedUnitPrice},
|
||||||
|
estimated_amount = #{purchaseRequest.estimatedAmount},
|
||||||
<if test="purchaseRequest.unit != null">unit = #{purchaseRequest.unit},</if>
|
<if test="purchaseRequest.unit != null">unit = #{purchaseRequest.unit},</if>
|
||||||
<if test="purchaseRequest.expectedDate != null">expected_date = #{purchaseRequest.expectedDate},</if>
|
<if test="purchaseRequest.expectedDate != null">expected_date = #{purchaseRequest.expectedDate},</if>
|
||||||
<if test="purchaseRequest.applicantId != null">applicant_id = #{purchaseRequest.applicantId},</if>
|
<if test="purchaseRequest.applicantId != null">applicant_id = #{purchaseRequest.applicantId},</if>
|
||||||
|
|||||||
@ -37,6 +37,13 @@
|
|||||||
and lower(concat_ws(' ', pi.product_name, pi.brand, pi.category, pi.spec, pi.unit, wa.area_name))
|
and lower(concat_ws(' ', pi.product_name, pi.brand, pi.category, pi.spec, pi.unit, wa.area_name))
|
||||||
like concat('%', lower(#{productName}), '%')
|
like concat('%', lower(#{productName}), '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="params.keywordTokens != null and params.keywordTokens.size() > 0">
|
||||||
|
<foreach collection="params.keywordTokens" item="token">
|
||||||
|
and lower(concat_ws(' ', pi.product_name, pi.brand, pi.category, pi.spec, pi.unit,
|
||||||
|
pi.product_code, wa.area_name, wa.area_code))
|
||||||
|
like concat('%', #{token}, '%')
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
order by si.id desc
|
order by si.id desc
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.warehouse.mapper.StockLotMapper">
|
||||||
|
<resultMap id="StockLotResult" type="StockLot">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="lotNo" column="lot_no"/>
|
||||||
|
<result property="areaId" column="area_id"/>
|
||||||
|
<result property="areaName" column="area_name"/>
|
||||||
|
<result property="productId" column="product_id"/>
|
||||||
|
<result property="productName" column="product_name"/>
|
||||||
|
<result property="sourceProjectId" column="source_project_id"/>
|
||||||
|
<result property="sourceProjectName" column="source_project_name"/>
|
||||||
|
<result property="expenseCategoryId" column="expense_category_id"/>
|
||||||
|
<result property="expenseCategoryName" column="expense_category_name"/>
|
||||||
|
<result property="purchaseRequestId" column="purchase_request_id"/>
|
||||||
|
<result property="purchaseRequestNo" column="purchase_request_no"/>
|
||||||
|
<result property="arrivalId" column="arrival_id"/>
|
||||||
|
<result property="inRecordId" column="in_record_id"/>
|
||||||
|
<result property="receivedQuantity" column="received_quantity"/>
|
||||||
|
<result property="availableQuantity" column="available_quantity"/>
|
||||||
|
<result property="unitCost" column="unit_cost"/>
|
||||||
|
<result property="lotStatus" column="lot_status"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
<result property="createBy" column="create_by"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateBy" column="update_by"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectStockLotVo">
|
||||||
|
select sl.*, wa.area_name, pi.product_name, project.project_name as source_project_name,
|
||||||
|
ec.category_name as expense_category_name, pr.request_no as purchase_request_no
|
||||||
|
from stock_lot sl
|
||||||
|
left join warehouse_area wa on wa.id = sl.area_id
|
||||||
|
left join product_info pi on pi.id = sl.product_id
|
||||||
|
left join project_info project on project.id = sl.source_project_id
|
||||||
|
left join expense_category ec on ec.id = sl.expense_category_id
|
||||||
|
left join purchase_request pr on pr.id = sl.purchase_request_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectStockLotList" parameterType="StockLot" resultMap="StockLotResult">
|
||||||
|
<include refid="selectStockLotVo"/>
|
||||||
|
<where>
|
||||||
|
<if test="areaId != null">and sl.area_id = #{areaId}</if>
|
||||||
|
<if test="productId != null">and sl.product_id = #{productId}</if>
|
||||||
|
<if test="sourceProjectId != null">and sl.source_project_id = #{sourceProjectId}</if>
|
||||||
|
<if test="lotStatus != null">and sl.lot_status = #{lotStatus}</if>
|
||||||
|
</where>
|
||||||
|
order by sl.create_time, sl.id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAvailableLotsForUpdate" resultMap="StockLotResult">
|
||||||
|
select * from stock_lot
|
||||||
|
where area_id = #{areaId} and product_id = #{productId}
|
||||||
|
and lot_status = 0 and available_quantity > 0
|
||||||
|
order by create_time, id for update
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectStockLotByInRecordId" parameterType="Long" resultMap="StockLotResult">
|
||||||
|
<include refid="selectStockLotVo"/> where sl.in_record_id = #{inRecordId} limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertStockLot" parameterType="StockLot" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into stock_lot(lot_no, area_id, product_id, source_project_id, expense_category_id,
|
||||||
|
purchase_request_id, arrival_id, in_record_id, received_quantity,
|
||||||
|
available_quantity, unit_cost, lot_status, remark, create_by, create_time)
|
||||||
|
values(#{lotNo}, #{areaId}, #{productId}, #{sourceProjectId}, #{expenseCategoryId},
|
||||||
|
#{purchaseRequestId}, #{arrivalId}, #{inRecordId}, #{receivedQuantity},
|
||||||
|
#{availableQuantity}, #{unitCost}, #{lotStatus}, #{remark}, #{createBy}, sysdate())
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="deductStockLot">
|
||||||
|
update stock_lot
|
||||||
|
set available_quantity = available_quantity - #{quantity},
|
||||||
|
lot_status = case when available_quantity - #{quantity} = 0 then 1 else lot_status end,
|
||||||
|
update_time = sysdate()
|
||||||
|
where id = #{id} and lot_status = 0 and available_quantity >= #{quantity}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<insert id="insertOutRecordLot">
|
||||||
|
insert into out_record_lot(out_record_id, stock_lot_id, source_project_id, usage_project_id,
|
||||||
|
quantity, unit_cost, amount, create_time)
|
||||||
|
values(#{outRecordId}, #{stockLotId}, #{sourceProjectId}, #{usageProjectId},
|
||||||
|
#{quantity}, #{unitCost}, #{amount}, sysdate())
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@ -31,6 +31,12 @@
|
|||||||
<include refid="selectWarehouseAreaVo"/> where id = #{id}
|
<include refid="selectWarehouseAreaVo"/> where id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectWarehouseAreaByNameOrCode" resultMap="WarehouseAreaResult">
|
||||||
|
<include refid="selectWarehouseAreaVo"/>
|
||||||
|
where area_name = #{value} or area_code = #{value}
|
||||||
|
order by id limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="checkAreaCodeUnique" parameterType="String" resultMap="WarehouseAreaResult">
|
<select id="checkAreaCodeUnique" parameterType="String" resultMap="WarehouseAreaResult">
|
||||||
<include refid="selectWarehouseAreaVo"/> where area_code = #{areaCode} limit 1
|
<include refid="selectWarehouseAreaVo"/> where area_code = #{areaCode} limit 1
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@ -27,6 +27,12 @@
|
|||||||
<result property="updateTime" column="update_time"/>
|
<result property="updateTime" column="update_time"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap type="WmsBatchJob" id="WmsBatchItemStatsResult">
|
||||||
|
<result property="totalCount" column="total_count"/>
|
||||||
|
<result property="successCount" column="success_count"/>
|
||||||
|
<result property="failCount" column="fail_count"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectWmsBatchItemVo">
|
<sql id="selectWmsBatchItemVo">
|
||||||
select id, job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg, ref_id,
|
select id, job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg, ref_id,
|
||||||
area_name, product_name, brand, category, spec, unit, quantity, operator_name, type_code,
|
area_name, product_name, brand, category, spec, unit, quantity, operator_name, type_code,
|
||||||
@ -52,6 +58,13 @@
|
|||||||
select count(1) from wms_batch_item where job_id = #{jobId} and status = #{status}
|
select count(1) from wms_batch_item where job_id = #{jobId} and status = #{status}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectWmsBatchItemStats" parameterType="Long" resultMap="WmsBatchItemStatsResult">
|
||||||
|
select count(1) as total_count,
|
||||||
|
coalesce(sum(case when status = 1 then 1 else 0 end), 0) as success_count,
|
||||||
|
coalesce(sum(case when status = 2 then 1 else 0 end), 0) as fail_count
|
||||||
|
from wms_batch_item where job_id = #{jobId}
|
||||||
|
</select>
|
||||||
|
|
||||||
<insert id="insertWmsBatchItem" parameterType="WmsBatchItem" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertWmsBatchItem" parameterType="WmsBatchItem" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into wms_batch_item(job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg,
|
insert into wms_batch_item(job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg,
|
||||||
ref_id, area_name, product_name, brand, category, spec, unit, quantity,
|
ref_id, area_name, product_name, brand, category, spec, unit, quantity,
|
||||||
@ -61,6 +74,19 @@
|
|||||||
#{operatorName}, #{typeCode}, #{sourceNo}, #{remark}, sysdate())
|
#{operatorName}, #{typeCode}, #{sourceNo}, #{remark}, sysdate())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<insert id="insertWmsBatchItemBatch">
|
||||||
|
insert into wms_batch_item(job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg,
|
||||||
|
ref_id, area_name, product_name, brand, category, spec, unit, quantity,
|
||||||
|
operator_name, type_code, source_no, remark, create_time)
|
||||||
|
values
|
||||||
|
<foreach collection="items" item="item" separator=",">
|
||||||
|
(#{item.jobId}, #{item.rowNo}, #{item.actionType}, #{item.rawText}, #{item.parsedJson}, #{item.status},
|
||||||
|
#{item.errorMsg}, #{item.warningMsg}, #{item.refId}, #{item.areaName}, #{item.productName}, #{item.brand},
|
||||||
|
#{item.category}, #{item.spec}, #{item.unit}, #{item.quantity}, #{item.operatorName}, #{item.typeCode},
|
||||||
|
#{item.sourceNo}, #{item.remark}, sysdate())
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
<update id="updateWmsBatchItem" parameterType="WmsBatchItem">
|
<update id="updateWmsBatchItem" parameterType="WmsBatchItem">
|
||||||
update wms_batch_item
|
update wms_batch_item
|
||||||
<set>
|
<set>
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
<if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if>
|
<if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if>
|
||||||
<if test="bizType != null and bizType != ''">and biz_type = #{bizType}</if>
|
<if test="bizType != null and bizType != ''">and biz_type = #{bizType}</if>
|
||||||
<if test="status != null">and status = #{status}</if>
|
<if test="status != null">and status = #{status}</if>
|
||||||
|
<if test="createBy != null and createBy != ''">and create_by = #{createBy}</if>
|
||||||
</where>
|
</where>
|
||||||
order by id desc
|
order by id desc
|
||||||
</select>
|
</select>
|
||||||
@ -64,4 +65,10 @@
|
|||||||
</set>
|
</set>
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="claimWmsBatchJob" parameterType="Long">
|
||||||
|
update wms_batch_job
|
||||||
|
set status = 3, progress = 0, error_msg = null, update_time = sysdate()
|
||||||
|
where id = #{id} and status = 2
|
||||||
|
</update>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
57
ruoyi-ui/src/api/project/project.js
Normal file
57
ruoyi-ui/src/api/project/project.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function listProject(query) {
|
||||||
|
return request({ url: '/project/info/list', method: 'get', params: query })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProject(id) {
|
||||||
|
return request({ url: '/project/info/' + id, method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectOptions(keyword, includeArchived = false) {
|
||||||
|
return request({ url: '/project/info/options', method: 'get', params: { keyword, includeArchived } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addProject(data) {
|
||||||
|
return request({ url: '/project/info', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateProject(data) {
|
||||||
|
return request({ url: '/project/info', method: 'put', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delProject(id) {
|
||||||
|
return request({ url: '/project/info/' + id, method: 'delete' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listProjectBudget(projectId) {
|
||||||
|
return request({ url: `/project/info/${projectId}/budgets`, method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveProjectBudget(data) {
|
||||||
|
return request({ url: '/project/info/budget', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listFundLedger(query) {
|
||||||
|
return request({ url: '/project/info/ledger/list', method: 'get', params: query })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function categoryOptions() {
|
||||||
|
return request({ url: '/project/info/category/options', method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listCategory(query) {
|
||||||
|
return request({ url: '/project/info/category/list', method: 'get', params: query })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addCategory(data) {
|
||||||
|
return request({ url: '/project/info/category', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCategory(data) {
|
||||||
|
return request({ url: '/project/info/category', method: 'put', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delCategory(id) {
|
||||||
|
return request({ url: '/project/info/category/' + id, method: 'delete' })
|
||||||
|
}
|
||||||
@ -11,3 +11,7 @@ export function getStock(id) {
|
|||||||
export function outStock(data) {
|
export function outStock(data) {
|
||||||
return request({ url: '/warehouse/stock/out', method: 'post', data })
|
return request({ url: '/warehouse/stock/out', method: 'post', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listStockLots(query) {
|
||||||
|
return request({ url: '/warehouse/stock/lots', method: 'get', params: query })
|
||||||
|
}
|
||||||
|
|||||||
19
ruoyi-ui/src/utils/echarts.js
Normal file
19
ruoyi-ui/src/utils/echarts.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as echarts from 'echarts/core'
|
||||||
|
import { BarChart, GaugeChart, LineChart, PieChart, RadarChart } from 'echarts/charts'
|
||||||
|
import { GridComponent, LegendComponent, RadarComponent, TooltipComponent } from 'echarts/components'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
BarChart,
|
||||||
|
GaugeChart,
|
||||||
|
LineChart,
|
||||||
|
PieChart,
|
||||||
|
RadarChart,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
RadarComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
CanvasRenderer
|
||||||
|
])
|
||||||
|
|
||||||
|
export default echarts
|
||||||
@ -3,8 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import echarts from '@/utils/echarts'
|
||||||
require('echarts/theme/macarons') // echarts theme
|
|
||||||
import resize from './mixins/resize'
|
import resize from './mixins/resize'
|
||||||
|
|
||||||
const animationDuration = 6000
|
const animationDuration = 6000
|
||||||
@ -44,7 +43,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initChart() {
|
initChart() {
|
||||||
this.chart = echarts.init(this.$el, 'macarons')
|
this.chart = echarts.init(this.$el)
|
||||||
|
|
||||||
this.chart.setOption({
|
this.chart.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import echarts from '@/utils/echarts'
|
||||||
require('echarts/theme/macarons') // echarts theme
|
|
||||||
import resize from './mixins/resize'
|
import resize from './mixins/resize'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -58,7 +57,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initChart() {
|
initChart() {
|
||||||
this.chart = echarts.init(this.$el, 'macarons')
|
this.chart = echarts.init(this.$el)
|
||||||
this.setOptions(this.chartData)
|
this.setOptions(this.chartData)
|
||||||
},
|
},
|
||||||
setOptions({ expectedData, actualData } = {}) {
|
setOptions({ expectedData, actualData } = {}) {
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import echarts from '@/utils/echarts'
|
||||||
require('echarts/theme/macarons') // echarts theme
|
|
||||||
import resize from './mixins/resize'
|
import resize from './mixins/resize'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -42,7 +41,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initChart() {
|
initChart() {
|
||||||
this.chart = echarts.init(this.$el, 'macarons')
|
this.chart = echarts.init(this.$el)
|
||||||
|
|
||||||
this.chart.setOption({
|
this.chart.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts'
|
import echarts from '@/utils/echarts'
|
||||||
require('echarts/theme/macarons') // echarts theme
|
|
||||||
import resize from './mixins/resize'
|
import resize from './mixins/resize'
|
||||||
|
|
||||||
const animationDuration = 3000
|
const animationDuration = 3000
|
||||||
@ -44,7 +43,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initChart() {
|
initChart() {
|
||||||
this.chart = echarts.init(this.$el, 'macarons')
|
this.chart = echarts.init(this.$el)
|
||||||
|
|
||||||
this.chart.setOption({
|
this.chart.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
|||||||
@ -8,12 +8,12 @@
|
|||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<el-button type="primary" icon="el-icon-top-right" @click="$router.push('/warehouse/outRecord')" v-hasPermi="['warehouse:out:list']">出库登记</el-button>
|
<el-button type="primary" icon="el-icon-top-right" @click="$router.push('/warehouse/outRecord')" v-hasPermi="['warehouse:out:list']">出库登记</el-button>
|
||||||
<el-button type="success" icon="el-icon-shopping-cart-2" @click="$router.push('/purchase')" v-hasPermi="['warehouse:purchase:list']">采购申请</el-button>
|
<el-button type="success" icon="el-icon-shopping-cart-2" @click="$router.push('/purchase')" v-hasPermi="['warehouse:purchase:list']">采购申请</el-button>
|
||||||
<el-button type="primary" icon="el-icon-upload2" @click="$router.push('/warehouse/batch-import')">批量导入</el-button>
|
<el-button type="primary" icon="el-icon-upload2" @click="$router.push('/warehouse/batch-import')" v-hasPermi="['warehouse:in:add', 'warehouse:stock:out', 'warehouse:out:add', 'warehouse:check:add', 'warehouse:product:add', 'warehouse:area:add']">批量导入</el-button>
|
||||||
<el-button type="primary" plain icon="el-icon-mobile-phone" @click="$router.push('/mobile')">移动端入口</el-button>
|
<el-button type="primary" plain icon="el-icon-mobile-phone" @click="$router.push('/mobile')">移动端入口</el-button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="agent-panel">
|
<section class="agent-panel" v-hasPermi="['ai:assistant:use']">
|
||||||
<div class="agent-title">
|
<div class="agent-title">
|
||||||
<div>
|
<div>
|
||||||
<h2>智能库存助手</h2>
|
<h2>智能库存助手</h2>
|
||||||
@ -68,14 +68,19 @@
|
|||||||
<el-button type="primary" size="mini" plain @click="selectAgentResult(scope.row)">选择</el-button>
|
<el-button type="primary" size="mini" plain @click="selectAgentResult(scope.row)">选择</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="库区" prop="areaName" width="120" />
|
<el-table-column v-if="agent.plan.action !== 'query_purchase'" label="库区" prop="areaName" width="120" />
|
||||||
|
<el-table-column v-else label="申请单号" prop="recordNo" width="165" />
|
||||||
<el-table-column label="产品" min-width="220">
|
<el-table-column label="产品" min-width="220">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
||||||
<div class="product-meta">{{ productMeta(scope.row) }}</div>
|
<div class="product-meta">{{ productMeta(scope.row) }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="库存" prop="stockNum" align="right" width="110" />
|
<el-table-column v-if="agent.plan.action === 'query_purchase'" label="项目" prop="projectName" min-width="150" />
|
||||||
|
<el-table-column v-if="agent.plan.action === 'query_purchase'" label="经费科目" prop="expenseCategoryName" width="110" />
|
||||||
|
<el-table-column v-if="agent.plan.action === 'query_purchase'" label="采购状态" prop="purchaseStatusName" width="100" />
|
||||||
|
<el-table-column v-if="agent.plan.action === 'query_purchase'" label="到货" width="130"><template slot-scope="scope">{{ scope.row.acceptedQuantity || 0 }} / {{ scope.row.quantity || 0 }}</template></el-table-column>
|
||||||
|
<el-table-column v-else label="库存" prop="stockNum" align="right" width="110" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -193,6 +198,9 @@ export default {
|
|||||||
{ label: '规格', value: plan.spec },
|
{ label: '规格', value: plan.spec },
|
||||||
{ label: '单位', value: plan.unit },
|
{ label: '单位', value: plan.unit },
|
||||||
{ label: '数量', value: plan.quantity },
|
{ label: '数量', value: plan.quantity },
|
||||||
|
{ label: '来源项目', value: plan.sourceProjectName },
|
||||||
|
{ label: '经费科目', value: plan.expenseCategoryName },
|
||||||
|
{ label: '使用项目', value: plan.usageProjectName },
|
||||||
{ label: '操作人', value: plan.operatorName },
|
{ label: '操作人', value: plan.operatorName },
|
||||||
{ label: '当前库存', value: plan.currentStock },
|
{ label: '当前库存', value: plan.currentStock },
|
||||||
{ label: '预计库存', value: plan.afterStock },
|
{ label: '预计库存', value: plan.afterStock },
|
||||||
@ -254,10 +262,24 @@ export default {
|
|||||||
if (!this.agent.plan || !item) {
|
if (!this.agent.plan || !item) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const plan = this.completePlanWithResult(this.agent.plan, item)
|
const text = this.selectionInstruction(this.agent.plan, item)
|
||||||
this.agent.plan = plan
|
this.agent.loading = true
|
||||||
this.agent.contextResults = [item]
|
analyzeAgent({ text, contextResults: [item] }).then(res => {
|
||||||
this.$modal.msgSuccess('已选择:' + (item.productName || '库存项'))
|
this.agent.plan = res.data
|
||||||
|
this.agent.contextResults = [item]
|
||||||
|
this.$modal.msgSuccess('已选择:' + (item.productName || '库存项'))
|
||||||
|
}).finally(() => { this.agent.loading = false })
|
||||||
|
},
|
||||||
|
selectionInstruction(plan, item) {
|
||||||
|
const quantity = plan.quantity || 1
|
||||||
|
const unit = item.unit || plan.unit || '个'
|
||||||
|
const project = plan.action === 'create_in_record'
|
||||||
|
? (plan.sourceProjectName ? `,来源项目${plan.sourceProjectName}` : '') + (plan.expenseCategoryName ? `,经费科目${plan.expenseCategoryName}` : '')
|
||||||
|
: (plan.usageProjectName ? `,使用项目${plan.usageProjectName}` : '')
|
||||||
|
const operator = plan.operatorName ? `,操作人${plan.operatorName}` : ''
|
||||||
|
if (plan.action === 'create_in_record') return `第一个入库${quantity}${unit}${project}${operator}`
|
||||||
|
if (plan.action === 'create_stock_check') return `盘点第一个实际${quantity}${unit}${operator}`
|
||||||
|
return `第一个出库${quantity}${unit}${project}${operator}`
|
||||||
},
|
},
|
||||||
completePlanWithResult(source, item) {
|
completePlanWithResult(source, item) {
|
||||||
const plan = Object.assign({}, source, {
|
const plan = Object.assign({}, source, {
|
||||||
@ -304,6 +326,7 @@ export default {
|
|||||||
actionLabel(action) {
|
actionLabel(action) {
|
||||||
const map = {
|
const map = {
|
||||||
query_stock: '查询库存',
|
query_stock: '查询库存',
|
||||||
|
query_purchase: '查询采购状态',
|
||||||
create_in_record: '入库登记',
|
create_in_record: '入库登记',
|
||||||
confirm_in_record: '确认入库',
|
confirm_in_record: '确认入库',
|
||||||
create_out_record: '出库',
|
create_out_record: '出库',
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="mobile-app">
|
<div class="mobile-app">
|
||||||
<header class="mobile-header">
|
<header class="mobile-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="mobile-title">库存管理</div>
|
<div class="mobile-title">仓储与采购</div>
|
||||||
<div class="mobile-subtitle">{{ nickName || name || '移动端' }}</div>
|
<div class="mobile-subtitle">{{ nickName || name || '移动端' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button size="mini" plain icon="el-icon-switch-button" @click="handleLogout">退出</el-button>
|
<el-button size="mini" plain icon="el-icon-switch-button" @click="handleLogout">退出</el-button>
|
||||||
@ -29,7 +29,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mobile-agent">
|
<div class="mobile-agent" v-hasPermi="['ai:assistant:use']">
|
||||||
<div class="agent-head">
|
<div class="agent-head">
|
||||||
<div>
|
<div>
|
||||||
<div class="agent-title">智能库存助手</div>
|
<div class="agent-title">智能库存助手</div>
|
||||||
@ -79,10 +79,13 @@
|
|||||||
<article v-for="(item, index) in (agent.plan.results || [])" :key="'agent-result-' + index + '-' + (item.id || item.productId || '')" class="agent-stock">
|
<article v-for="(item, index) in (agent.plan.results || [])" :key="'agent-result-' + index + '-' + (item.id || item.productId || '')" class="agent-stock">
|
||||||
<div>
|
<div>
|
||||||
<div class="card-title">第 {{ index + 1 }} 项:{{ item.productName || '-' }}</div>
|
<div class="card-title">第 {{ index + 1 }} 项:{{ item.productName || '-' }}</div>
|
||||||
<div class="meta-line">{{ item.areaName || '-' }} | {{ productMeta(item) }}</div>
|
<div class="meta-line" v-if="agent.plan.action === 'query_purchase'">{{ item.projectName || '-' }} | {{ item.expenseCategoryName || '-' }} | {{ item.recordNo || '-' }}</div>
|
||||||
|
<div class="meta-line" v-else>{{ item.areaName || '-' }} | {{ productMeta(item) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="agent-stock-action">
|
<div class="agent-stock-action">
|
||||||
<strong>{{ item.stockNum === undefined || item.stockNum === null ? '-' : item.stockNum }}</strong>
|
<strong v-if="agent.plan.action === 'query_purchase'">{{ item.purchaseStatusName }} {{ item.acceptedQuantity || 0 }}/{{ item.quantity || 0 }}</strong>
|
||||||
|
<strong v-else>{{ item.stockNum === undefined || item.stockNum === null ? '-' : item.stockNum }}</strong>
|
||||||
|
<el-button v-if="agent.plan.action === 'query_purchase' && item.id" type="success" size="mini" plain @click="openAgentPurchaseArrival(item)">到货明细</el-button>
|
||||||
<el-button v-if="canSelectAgentResult(agent.plan)" type="primary" size="mini" plain @click="selectAgentResult(item)">选择</el-button>
|
<el-button v-if="canSelectAgentResult(agent.plan)" type="primary" size="mini" plain @click="selectAgentResult(item)">选择</el-button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -90,13 +93,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quick-panel">
|
<div class="quick-panel">
|
||||||
<button @click="openInForm">入库登记</button>
|
<button @click="openInForm" v-hasPermi="['warehouse:in:add']">入库登记</button>
|
||||||
<button @click="openOutForm">出库登记</button>
|
<button @click="openOutForm" v-hasPermi="['warehouse:stock:out', 'warehouse:out:add']">出库登记</button>
|
||||||
<button @click="openCheckForm">盘库登记</button>
|
<button @click="openCheckForm" v-hasPermi="['warehouse:check:add']">盘库登记</button>
|
||||||
<button @click="switchTab('stock')">查库存</button>
|
<button @click="switchTab('stock')" v-hasPermi="['warehouse:stock:list']">查库存</button>
|
||||||
|
<button @click="switchTab('purchase')" v-hasPermi="['warehouse:purchase:list', 'warehouse:purchase:manage']">采购到货</button>
|
||||||
<button @click="switchTab('product')" v-hasPermi="['warehouse:product:list']">产品维护</button>
|
<button @click="switchTab('product')" v-hasPermi="['warehouse:product:list']">产品维护</button>
|
||||||
<button @click="switchTab('area')" v-hasPermi="['warehouse:area:list']">库区维护</button>
|
<button @click="switchTab('area')" v-hasPermi="['warehouse:area:list']">库区维护</button>
|
||||||
<button @click="switchTab('batch')">批量导入</button>
|
<button @click="switchTab('batch')" v-hasPermi="['warehouse:in:add', 'warehouse:stock:out', 'warehouse:out:add', 'warehouse:check:add', 'warehouse:product:add', 'warehouse:area:add']">批量导入</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-title">库存概览</div>
|
<div class="panel-title">库存概览</div>
|
||||||
@ -138,6 +142,58 @@
|
|||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section v-show="activeTab === 'purchase'" class="mobile-section">
|
||||||
|
<div class="section-head">
|
||||||
|
<div>
|
||||||
|
<div class="section-title">采购与到货</div>
|
||||||
|
<div class="section-subtitle">查看全部采购进度,本人或采购管理员可登记和验收到货</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-panel purchase-filter">
|
||||||
|
<el-input v-model="purchaseQuery.materialName" placeholder="物料、参数或编码" clearable />
|
||||||
|
<el-select v-model="purchaseQuery.purchaseStatus" placeholder="采购状态" clearable>
|
||||||
|
<el-option v-for="item in purchaseStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="purchaseQuery.applicantName" placeholder="申请人" clearable filterable>
|
||||||
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="item.userName" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" icon="el-icon-search" @click="loadPurchases(true)">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
<el-button class="full-button" type="primary" icon="el-icon-plus" @click="openPurchaseForm()" v-hasPermi="['warehouse:purchase:add', 'warehouse:purchase:manage']">新增采购申请</el-button>
|
||||||
|
|
||||||
|
<div v-loading="purchaseLoading">
|
||||||
|
<div v-if="!purchaseLoading && purchaseList.length === 0" class="empty-tip">暂无采购申请</div>
|
||||||
|
<article v-for="item in purchaseList" :key="'purchase-' + item.id" class="mobile-card purchase-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div>
|
||||||
|
<div class="card-title">{{ item.materialName || '-' }}</div>
|
||||||
|
<div class="card-subtitle">{{ item.requestNo }} · {{ item.applicantName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<el-tag size="mini" :type="purchaseStatusType(item.purchaseStatus)">{{ purchaseStatusLabel(item.purchaseStatus) }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="purchase-project">{{ item.projectName || '-' }} / {{ item.expenseCategoryName || '-' }}</div>
|
||||||
|
<div class="meta-line">{{ [item.materialBrand, item.category, item.materialParams].filter(Boolean).join(' / ') || '未填写物料明细' }}</div>
|
||||||
|
<div class="purchase-progress">
|
||||||
|
<span>采购 {{ item.quantity }} {{ item.unit || '' }}</span>
|
||||||
|
<span>已验收 {{ item.acceptedQuantity || 0 }}</span>
|
||||||
|
<span v-if="Number(item.pendingArrivalQuantity || 0) > 0" class="pending">待验收 {{ item.pendingArrivalQuantity }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="record-line">
|
||||||
|
<span>预计金额:¥{{ money(item.estimatedAmount) }}</span>
|
||||||
|
<span>期望:{{ item.expectedDate || '-' }}</span>
|
||||||
|
<span v-if="item.purchaseBatchNo">批次:{{ item.purchaseBatchNo }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions purchase-actions">
|
||||||
|
<el-button type="success" size="mini" icon="el-icon-box" @click="openPurchaseArrival(item)" v-hasPermi="['warehouse:purchase:list', 'warehouse:purchase:manage']">{{ canOperatePurchase(item) ? '到货/验收' : '到货明细' }}</el-button>
|
||||||
|
<el-button v-if="canOperatePurchase(item)" type="primary" size="mini" icon="el-icon-edit" @click="openPurchaseForm(item)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">修改</el-button>
|
||||||
|
<el-button v-if="canOperatePurchase(item) && Number(item.acceptedQuantity || 0) === 0" size="mini" icon="el-icon-refresh" @click="openPurchaseStatus(item)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">状态</el-button>
|
||||||
|
<el-button v-if="canOperatePurchase(item)" type="danger" size="mini" icon="el-icon-delete" @click="deletePurchaseItem(item)" v-hasPermi="['warehouse:purchase:remove', 'warehouse:purchase:manage']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<el-button v-if="purchaseList.length < purchaseTotal" class="full-button" plain icon="el-icon-more" @click="loadMorePurchases">加载更多({{ purchaseList.length }}/{{ purchaseTotal }})</el-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section v-show="activeTab === 'in'" class="mobile-section">
|
<section v-show="activeTab === 'in'" class="mobile-section">
|
||||||
<div class="filter-panel">
|
<div class="filter-panel">
|
||||||
<el-select v-model="inQuery.areaId" placeholder="库区" clearable filterable>
|
<el-select v-model="inQuery.areaId" placeholder="库区" clearable filterable>
|
||||||
@ -166,7 +222,7 @@
|
|||||||
<div class="meta-line">{{ productMeta(item) }}</div>
|
<div class="meta-line">{{ productMeta(item) }}</div>
|
||||||
<div class="record-line">
|
<div class="record-line">
|
||||||
<span>数量:{{ item.inNum }}</span>
|
<span>数量:{{ item.inNum }}</span>
|
||||||
<span>入库人:{{ item.inOperator || '-' }}</span>
|
<span>入库人:{{ item.inOperatorName || item.inOperator || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions" v-if="item.inStatus === 0">
|
<div class="card-actions" v-if="item.inStatus === 0">
|
||||||
<el-button type="primary" size="mini" icon="el-icon-edit" @click="openInForm(item)" v-hasPermi="['warehouse:in:edit']">修改</el-button>
|
<el-button type="primary" size="mini" icon="el-icon-edit" @click="openInForm(item)" v-hasPermi="['warehouse:in:edit']">修改</el-button>
|
||||||
@ -201,7 +257,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="meta-line">{{ productMeta(item) }}</div>
|
<div class="meta-line">{{ productMeta(item) }}</div>
|
||||||
<div class="record-line">
|
<div class="record-line">
|
||||||
<span>出库人:{{ item.outOperator || '-' }}</span>
|
<span>出库人:{{ item.outOperatorName || item.outOperator || '-' }}</span>
|
||||||
<span>{{ parseTime(item.outTime, '{m}-{d} {h}:{i}') }}</span>
|
<span>{{ parseTime(item.outTime, '{m}-{d} {h}:{i}') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -410,14 +466,43 @@
|
|||||||
|
|
||||||
<nav class="bottom-nav">
|
<nav class="bottom-nav">
|
||||||
<button :class="{ active: activeTab === 'home' }" @click="switchTab('home')"><i class="el-icon-s-home"></i><span>首页</span></button>
|
<button :class="{ active: activeTab === 'home' }" @click="switchTab('home')"><i class="el-icon-s-home"></i><span>首页</span></button>
|
||||||
<button :class="{ active: activeTab === 'stock' }" @click="switchTab('stock')"><i class="el-icon-box"></i><span>库存</span></button>
|
<button :class="{ active: activeTab === 'stock' }" @click="switchTab('stock')" v-hasPermi="['warehouse:stock:list']"><i class="el-icon-box"></i><span>库存</span></button>
|
||||||
<button :class="{ active: activeTab === 'in' }" @click="switchTab('in')"><i class="el-icon-bottom-left"></i><span>入库</span></button>
|
<button :class="{ active: activeTab === 'purchase' }" @click="switchTab('purchase')" v-hasPermi="['warehouse:purchase:list', 'warehouse:purchase:manage']"><i class="el-icon-shopping-cart-2"></i><span>采购</span></button>
|
||||||
<button :class="{ active: activeTab === 'out' }" @click="switchTab('out')"><i class="el-icon-top-right"></i><span>出库</span></button>
|
<button :class="{ active: activeTab === 'in' }" @click="switchTab('in')" v-hasPermi="['warehouse:in:list']"><i class="el-icon-bottom-left"></i><span>入库</span></button>
|
||||||
<button :class="{ active: activeTab === 'check' }" @click="switchTab('check')"><i class="el-icon-document-checked"></i><span>盘库</span></button>
|
<button :class="{ active: activeTab === 'out' }" @click="switchTab('out')" v-hasPermi="['warehouse:out:list']"><i class="el-icon-top-right"></i><span>出库</span></button>
|
||||||
|
<button :class="{ active: activeTab === 'check' }" @click="switchTab('check')" v-hasPermi="['warehouse:check:list']"><i class="el-icon-document-checked"></i><span>盘库</span></button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" custom-class="mobile-dialog" append-to-body>
|
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" custom-class="mobile-dialog" append-to-body>
|
||||||
<el-form ref="mobileForm" :model="form" :rules="formRules" label-width="82px">
|
<el-form ref="mobileForm" :model="form" :rules="formRules" label-width="82px">
|
||||||
|
<template v-if="dialogType === 'purchase'">
|
||||||
|
<el-form-item label="采购项目" prop="projectId">
|
||||||
|
<el-select v-model="form.projectId" placeholder="请选择项目" filterable>
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId">
|
||||||
|
<el-select v-model="form.expenseCategoryId" placeholder="请选择科目" filterable>
|
||||||
|
<el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="已有产品" prop="productId">
|
||||||
|
<el-select v-model="form.productId" placeholder="可选,选择后自动填充" clearable filterable @change="handlePurchaseProductChange">
|
||||||
|
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="物料名称" prop="materialName"><el-input v-model="form.materialName" /></el-form-item>
|
||||||
|
<el-form-item label="品牌"><el-input v-model="form.materialBrand" /></el-form-item>
|
||||||
|
<el-form-item label="类别"><el-input v-model="form.category" /></el-form-item>
|
||||||
|
<el-form-item label="参数规格"><el-input v-model="form.materialParams" /></el-form-item>
|
||||||
|
<el-form-item label="数量" prop="quantity"><el-input-number v-model="form.quantity" :min="0.001" :precision="3" :controls="false" /></el-form-item>
|
||||||
|
<el-form-item label="预计单价" prop="estimatedUnitPrice"><el-input-number v-model="form.estimatedUnitPrice" :min="0.01" :precision="2" :controls="false" /></el-form-item>
|
||||||
|
<el-form-item label="单位"><el-input v-model="form.unit" /></el-form-item>
|
||||||
|
<el-form-item label="期望到货"><el-date-picker v-model="form.expectedDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择日期" /></el-form-item>
|
||||||
|
<el-form-item label="采购链接" prop="purchaseLink"><el-input v-model="form.purchaseLink" /></el-form-item>
|
||||||
|
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="dialogType === 'in'">
|
<template v-if="dialogType === 'in'">
|
||||||
<el-form-item label="库区" prop="areaId">
|
<el-form-item label="库区" prop="areaId">
|
||||||
<el-select v-model="form.areaId" placeholder="请选择库区" filterable>
|
<el-select v-model="form.areaId" placeholder="请选择库区" filterable>
|
||||||
@ -429,6 +514,16 @@
|
|||||||
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="来源项目" prop="sourceProjectId">
|
||||||
|
<el-select v-model="form.sourceProjectId" placeholder="请选择来源项目" filterable>
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId">
|
||||||
|
<el-select v-model="form.expenseCategoryId" placeholder="请选择经费科目" filterable>
|
||||||
|
<el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="入库人" prop="inOperator">
|
<el-form-item label="入库人" prop="inOperator">
|
||||||
<el-select v-model="form.inOperator" placeholder="请选择入库人" filterable>
|
<el-select v-model="form.inOperator" placeholder="请选择入库人" filterable>
|
||||||
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
||||||
@ -457,6 +552,11 @@
|
|||||||
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="使用项目" prop="usageProjectId">
|
||||||
|
<el-select v-model="form.usageProjectId" placeholder="请选择使用项目" filterable>
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="出库人" prop="outOperator">
|
<el-form-item label="出库人" prop="outOperator">
|
||||||
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
||||||
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
||||||
@ -469,6 +569,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" /></el-form-item>
|
<el-form-item label="数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" /></el-form-item>
|
||||||
<el-form-item label="关联单号"><el-input v-model="form.sourceNo" /></el-form-item>
|
<el-form-item label="关联单号"><el-input v-model="form.sourceNo" /></el-form-item>
|
||||||
|
<el-form-item label="跨项目原因"><el-input v-model="form.crossProjectReason" type="textarea" placeholder="来源项目与使用项目不同时必填" /></el-form-item>
|
||||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item>
|
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" /></el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -528,6 +629,64 @@
|
|||||||
<el-button type="primary" @click="submitDialog">确定</el-button>
|
<el-button type="primary" @click="submitDialog">确定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="采购状态" :visible.sync="purchaseStatusOpen" custom-class="mobile-dialog" append-to-body>
|
||||||
|
<el-form ref="mobilePurchaseStatusForm" :model="purchaseStatusForm" :rules="purchaseStatusRules" label-width="82px">
|
||||||
|
<el-form-item label="采购状态" prop="purchaseStatus">
|
||||||
|
<el-select v-model="purchaseStatusForm.purchaseStatus">
|
||||||
|
<el-option v-for="item in manualPurchaseStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="进度说明"><el-input v-model="purchaseStatusForm.progressRemark" type="textarea" :rows="3" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer"><el-button @click="purchaseStatusOpen = false">取消</el-button><el-button type="primary" @click="submitPurchaseStatus">确定</el-button></div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="到货登记与验收" :visible.sync="purchaseArrivalOpen" custom-class="mobile-dialog mobile-arrival-dialog" append-to-body>
|
||||||
|
<div v-if="purchaseArrivalRequest" class="mobile-arrival-head">
|
||||||
|
<strong>{{ purchaseArrivalRequest.materialName }}</strong>
|
||||||
|
<span>{{ purchaseArrivalRequest.projectName }} / {{ purchaseArrivalRequest.expenseCategoryName }}</span>
|
||||||
|
<div class="purchase-progress">
|
||||||
|
<span>采购 {{ purchaseArrivalRequest.quantity }} {{ purchaseArrivalRequest.unit || '' }}</span>
|
||||||
|
<span>已验收 {{ purchaseArrivalRequest.acceptedQuantity || 0 }}</span>
|
||||||
|
<span class="pending">可登记 {{ purchaseArrivalRequest.remainingQuantity || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-alert v-if="purchaseArrivalRequest && !canOperatePurchase(purchaseArrivalRequest)" title="当前为只读模式,仅申请人和采购管理员可以登记或验收到货" type="info" :closable="false" show-icon />
|
||||||
|
<el-form v-if="purchaseArrivalRequest && canOperatePurchase(purchaseArrivalRequest) && Number(purchaseArrivalRequest.remainingQuantity || 0) > 0" ref="mobileArrivalForm" :model="purchaseArrivalForm" :rules="purchaseArrivalRules" label-width="88px" class="mobile-arrival-form">
|
||||||
|
<el-form-item label="本次到货" prop="arrivalQuantity"><el-input-number v-model="purchaseArrivalForm.arrivalQuantity" :min="0.001" :max="Number(purchaseArrivalRequest.remainingQuantity || 0)" :precision="3" :controls="false" /></el-form-item>
|
||||||
|
<el-form-item label="实际单价" prop="actualUnitPrice"><el-input-number v-model="purchaseArrivalForm.actualUnitPrice" :min="0.01" :precision="2" :controls="false" /></el-form-item>
|
||||||
|
<el-form-item label="到货日期" prop="arrivalDate"><el-date-picker v-model="purchaseArrivalForm.arrivalDate" type="date" value-format="yyyy-MM-dd" /></el-form-item>
|
||||||
|
<el-form-item label="备注"><el-input v-model="purchaseArrivalForm.remark" /></el-form-item>
|
||||||
|
<el-button class="full-button" type="primary" icon="el-icon-plus" @click="submitPurchaseArrival">登记到货</el-button>
|
||||||
|
</el-form>
|
||||||
|
<div v-loading="purchaseArrivalLoading" class="mobile-arrival-list">
|
||||||
|
<div v-if="!purchaseArrivalLoading && purchaseArrivalList.length === 0" class="empty-tip">暂无到货记录</div>
|
||||||
|
<article v-for="item in purchaseArrivalList" :key="'arrival-' + item.id" class="arrival-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div>
|
||||||
|
<div class="card-title">{{ item.arrivalNo }}</div>
|
||||||
|
<div class="card-subtitle">{{ item.arrivalDate }} · {{ item.arrivalQuantity }} {{ purchaseArrivalRequest && purchaseArrivalRequest.unit || '' }}</div>
|
||||||
|
</div>
|
||||||
|
<el-tag size="mini" :type="acceptanceType(item.acceptanceStatus)">{{ acceptanceLabel(item.acceptanceStatus) }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="record-line"><span>单价:¥{{ money(item.actualUnitPrice) }}</span><span>金额:¥{{ money(item.actualAmount) }}</span></div>
|
||||||
|
<div v-if="item.remark || item.acceptanceRemark || item.revokeRemark" class="arrival-notes">
|
||||||
|
<div v-if="item.remark">到货:{{ item.remark }}</div>
|
||||||
|
<div v-if="item.acceptanceRemark">验收:{{ item.acceptanceRemark }}</div>
|
||||||
|
<div v-if="item.revokeRemark">撤销:{{ item.revokeRemark }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="purchaseArrivalRequest && canOperatePurchase(purchaseArrivalRequest)" class="card-actions purchase-actions">
|
||||||
|
<template v-if="Number(item.acceptanceStatus) === 0">
|
||||||
|
<el-button type="success" size="mini" icon="el-icon-check" @click="acceptMobileArrival(item, 1)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">验收通过</el-button>
|
||||||
|
<el-button type="warning" size="mini" icon="el-icon-close" @click="acceptMobileArrival(item, 2)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">拒收</el-button>
|
||||||
|
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteMobileArrival(item)" v-hasPermi="['warehouse:purchase:remove', 'warehouse:purchase:manage']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
<el-button v-else size="mini" icon="el-icon-refresh-left" @click="revokeMobileArrival(item)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">撤销验收</el-button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -540,9 +699,11 @@ import { listStock, outStock } from '@/api/warehouse/stock'
|
|||||||
import { listInRecord, getInRecord, addInRecord, updateInRecord, delInRecord, confirmInRecord } from '@/api/warehouse/inRecord'
|
import { listInRecord, getInRecord, addInRecord, updateInRecord, delInRecord, confirmInRecord } from '@/api/warehouse/inRecord'
|
||||||
import { listOutRecord, addOutRecord } from '@/api/warehouse/outRecord'
|
import { listOutRecord, addOutRecord } from '@/api/warehouse/outRecord'
|
||||||
import { listStockCheck, addStockCheck, confirmStockCheck } from '@/api/warehouse/stockCheck'
|
import { listStockCheck, addStockCheck, confirmStockCheck } from '@/api/warehouse/stockCheck'
|
||||||
|
import { listPurchase, getPurchase, addPurchase, updatePurchase, updatePurchaseStatus, delPurchase, listPurchaseArrival, addPurchaseArrival, acceptPurchaseArrival, revokePurchaseArrival, delPurchaseArrival } from '@/api/warehouse/purchase'
|
||||||
import { analyzeAgent, executeAgent } from '@/api/warehouse/agent'
|
import { analyzeAgent, executeAgent } from '@/api/warehouse/agent'
|
||||||
import { listBatchJob, uploadBatchFile, getBatchJob, listBatchItems, updateBatchItemAction, confirmBatchJob } from '@/api/warehouse/batchImport'
|
import { listBatchJob, uploadBatchFile, getBatchJob, listBatchItems, updateBatchItemAction, confirmBatchJob } from '@/api/warehouse/batchImport'
|
||||||
import { compressImageForUpload } from '@/utils/imageCompress'
|
import { compressImageForUpload } from '@/utils/imageCompress'
|
||||||
|
import { projectOptions, categoryOptions as expenseCategoryOptionApi } from '@/api/project/project'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MobileWarehouse',
|
name: 'MobileWarehouse',
|
||||||
@ -553,10 +714,15 @@ export default {
|
|||||||
areaOptions: [],
|
areaOptions: [],
|
||||||
productOptions: [],
|
productOptions: [],
|
||||||
userOptions: [],
|
userOptions: [],
|
||||||
|
projectOptionList: [],
|
||||||
|
expenseCategoryOptions: [],
|
||||||
stockList: [],
|
stockList: [],
|
||||||
inList: [],
|
inList: [],
|
||||||
outList: [],
|
outList: [],
|
||||||
checkList: [],
|
checkList: [],
|
||||||
|
purchaseList: [],
|
||||||
|
purchaseTotal: 0,
|
||||||
|
purchaseLoading: false,
|
||||||
productList: [],
|
productList: [],
|
||||||
areaList: [],
|
areaList: [],
|
||||||
summary: { stockTotal: 0, pendingIn: 0, pendingCheck: 0, recentOut: 0 },
|
summary: { stockTotal: 0, pendingIn: 0, pendingCheck: 0, recentOut: 0 },
|
||||||
@ -564,6 +730,7 @@ export default {
|
|||||||
inQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, inStatus: undefined },
|
inQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, inStatus: undefined },
|
||||||
outQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, spec: undefined },
|
outQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, spec: undefined },
|
||||||
checkQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, checkStatus: undefined },
|
checkQuery: { pageNum: 1, pageSize: 20, areaId: undefined, productName: undefined, brand: undefined, checkStatus: undefined },
|
||||||
|
purchaseQuery: { pageNum: 1, pageSize: 20, materialName: undefined, purchaseStatus: undefined, applicantName: undefined },
|
||||||
productQuery: { pageNum: 1, pageSize: 50, productName: undefined },
|
productQuery: { pageNum: 1, pageSize: 50, productName: undefined },
|
||||||
areaQuery: { pageNum: 1, pageSize: 50, areaName: undefined },
|
areaQuery: { pageNum: 1, pageSize: 50, areaName: undefined },
|
||||||
dialogOpen: false,
|
dialogOpen: false,
|
||||||
@ -571,6 +738,27 @@ export default {
|
|||||||
dialogTitle: '',
|
dialogTitle: '',
|
||||||
form: {},
|
form: {},
|
||||||
formRules: {},
|
formRules: {},
|
||||||
|
purchaseStatusOpen: false,
|
||||||
|
purchaseStatusForm: {},
|
||||||
|
purchaseStatusRules: { purchaseStatus: [{ required: true, message: '请选择采购状态', trigger: 'change' }] },
|
||||||
|
purchaseArrivalOpen: false,
|
||||||
|
purchaseArrivalLoading: false,
|
||||||
|
purchaseArrivalRequest: null,
|
||||||
|
purchaseArrivalList: [],
|
||||||
|
purchaseArrivalForm: {},
|
||||||
|
purchaseArrivalRules: {
|
||||||
|
arrivalQuantity: [{ required: true, message: '请输入到货数量', trigger: 'change' }],
|
||||||
|
actualUnitPrice: [{ required: true, message: '请输入实际单价', trigger: 'change' }],
|
||||||
|
arrivalDate: [{ required: true, message: '请选择到货日期', trigger: 'change' }]
|
||||||
|
},
|
||||||
|
purchaseStatusOptions: [
|
||||||
|
{ value: 0, label: '待采购', type: 'info' },
|
||||||
|
{ value: 1, label: '已下单', type: '' },
|
||||||
|
{ value: 2, label: '采购中', type: 'warning' },
|
||||||
|
{ value: 3, label: '部分到货', type: 'warning' },
|
||||||
|
{ value: 4, label: '已到齐', type: 'success' },
|
||||||
|
{ value: 5, label: '已取消', type: 'danger' }
|
||||||
|
],
|
||||||
agent: {
|
agent: {
|
||||||
text: '',
|
text: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -595,7 +783,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['name', 'nickName']),
|
...mapGetters(['name', 'nickName', 'id', 'permissions']),
|
||||||
|
canManagePurchase() {
|
||||||
|
return (this.permissions || []).includes('*:*:*') || (this.permissions || []).includes('warehouse:purchase:manage')
|
||||||
|
},
|
||||||
|
canViewPurchase() {
|
||||||
|
return this.canManagePurchase || (this.permissions || []).includes('warehouse:purchase:list')
|
||||||
|
},
|
||||||
|
manualPurchaseStatusOptions() {
|
||||||
|
return this.purchaseStatusOptions.filter(item => [0, 1, 2, 5].includes(item.value))
|
||||||
|
},
|
||||||
brandOptions() {
|
brandOptions() {
|
||||||
return this.distinctProductOptions('brand')
|
return this.distinctProductOptions('brand')
|
||||||
},
|
},
|
||||||
@ -621,6 +818,9 @@ export default {
|
|||||||
{ label: '规格', value: plan.spec },
|
{ label: '规格', value: plan.spec },
|
||||||
{ label: '单位', value: plan.unit },
|
{ label: '单位', value: plan.unit },
|
||||||
{ label: '数量', value: plan.quantity },
|
{ label: '数量', value: plan.quantity },
|
||||||
|
{ label: '来源项目', value: plan.sourceProjectName },
|
||||||
|
{ label: '经费科目', value: plan.expenseCategoryName },
|
||||||
|
{ label: '使用项目', value: plan.usageProjectName },
|
||||||
{ label: '人员', value: plan.operatorName },
|
{ label: '人员', value: plan.operatorName },
|
||||||
{ label: '当前', value: plan.currentStock },
|
{ label: '当前', value: plan.currentStock },
|
||||||
{ label: '预计', value: plan.afterStock },
|
{ label: '预计', value: plan.afterStock },
|
||||||
@ -636,6 +836,186 @@ export default {
|
|||||||
this.stopBatchPolling()
|
this.stopBatchPolling()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
money(value) {
|
||||||
|
return Number(value || 0).toFixed(2)
|
||||||
|
},
|
||||||
|
purchaseStatusLabel(status) {
|
||||||
|
const item = this.purchaseStatusOptions.find(option => option.value === Number(status))
|
||||||
|
return item ? item.label : '未知'
|
||||||
|
},
|
||||||
|
purchaseStatusType(status) {
|
||||||
|
const item = this.purchaseStatusOptions.find(option => option.value === Number(status))
|
||||||
|
return item ? item.type : 'info'
|
||||||
|
},
|
||||||
|
acceptanceLabel(status) {
|
||||||
|
return { 0: '待验收', 1: '已通过', 2: '已拒收' }[Number(status)] || '未知'
|
||||||
|
},
|
||||||
|
acceptanceType(status) {
|
||||||
|
return { 0: 'warning', 1: 'success', 2: 'danger' }[Number(status)] || 'info'
|
||||||
|
},
|
||||||
|
canOperatePurchase(row) {
|
||||||
|
return this.canManagePurchase || Number(row && row.applicantId) === Number(this.id)
|
||||||
|
},
|
||||||
|
hasAnyPermission(required) {
|
||||||
|
const current = this.permissions || []
|
||||||
|
return current.includes('*:*:*') || required.some(permission => current.includes(permission))
|
||||||
|
},
|
||||||
|
openAgentPurchaseArrival(item) {
|
||||||
|
this.switchTab('purchase')
|
||||||
|
getPurchase(item.id).then(res => this.openPurchaseArrival(res.data))
|
||||||
|
},
|
||||||
|
loadPurchases(reset = false) {
|
||||||
|
if (!this.canViewPurchase) return
|
||||||
|
if (reset) this.purchaseQuery.pageNum = 1
|
||||||
|
this.purchaseLoading = true
|
||||||
|
listPurchase(this.purchaseQuery).then(res => {
|
||||||
|
const rows = res.rows || []
|
||||||
|
this.purchaseList = reset || this.purchaseQuery.pageNum === 1 ? rows : this.purchaseList.concat(rows)
|
||||||
|
this.purchaseTotal = res.total || 0
|
||||||
|
}).finally(() => { this.purchaseLoading = false })
|
||||||
|
},
|
||||||
|
loadMorePurchases() {
|
||||||
|
if (this.purchaseLoading || this.purchaseList.length >= this.purchaseTotal) return
|
||||||
|
this.purchaseQuery.pageNum += 1
|
||||||
|
this.loadPurchases(false)
|
||||||
|
},
|
||||||
|
openPurchaseForm(row) {
|
||||||
|
this.dialogType = 'purchase'
|
||||||
|
this.dialogTitle = row && row.id ? '修改采购申请' : '新增采购申请'
|
||||||
|
this.formRules = {
|
||||||
|
projectId: [{ required: true, message: '请选择采购项目', trigger: 'change' }],
|
||||||
|
expenseCategoryId: [{ required: true, message: '请选择经费科目', trigger: 'change' }],
|
||||||
|
materialName: [{ required: true, message: '请输入物料名称', trigger: 'blur' }],
|
||||||
|
quantity: [{ required: true, message: '请输入需求数量', trigger: 'change' }],
|
||||||
|
estimatedUnitPrice: [{ required: true, message: '请输入预计单价', trigger: 'change' }],
|
||||||
|
purchaseLink: [{ pattern: /^https?:\/\//i, message: '链接必须以 http:// 或 https:// 开头', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
const setForm = data => {
|
||||||
|
this.form = Object.assign({
|
||||||
|
id: undefined,
|
||||||
|
projectId: undefined,
|
||||||
|
expenseCategoryId: undefined,
|
||||||
|
productId: undefined,
|
||||||
|
materialName: undefined,
|
||||||
|
materialBrand: undefined,
|
||||||
|
category: undefined,
|
||||||
|
materialParams: undefined,
|
||||||
|
quantity: 1,
|
||||||
|
estimatedUnitPrice: undefined,
|
||||||
|
unit: undefined,
|
||||||
|
expectedDate: undefined,
|
||||||
|
purchaseLink: undefined,
|
||||||
|
remark: undefined
|
||||||
|
}, data || {})
|
||||||
|
this.dialogOpen = true
|
||||||
|
this.$nextTick(() => this.$refs.mobileForm && this.$refs.mobileForm.clearValidate())
|
||||||
|
}
|
||||||
|
if (row && row.id) getPurchase(row.id).then(res => setForm(res.data))
|
||||||
|
else setForm()
|
||||||
|
},
|
||||||
|
handlePurchaseProductChange(productId) {
|
||||||
|
if (!productId) return
|
||||||
|
const product = this.productOptions.find(item => Number(item.id) === Number(productId))
|
||||||
|
if (!product) return
|
||||||
|
this.form.materialName = product.productName
|
||||||
|
this.form.materialBrand = product.brand
|
||||||
|
this.form.category = product.category
|
||||||
|
this.form.materialParams = product.spec
|
||||||
|
this.form.unit = product.unit
|
||||||
|
},
|
||||||
|
deletePurchaseItem(row) {
|
||||||
|
this.$modal.confirm('确认删除采购申请“' + (row.requestNo || '-') + '”?').then(() => delPurchase(row.id)).then(() => {
|
||||||
|
this.$modal.msgSuccess('采购申请已删除')
|
||||||
|
this.loadPurchases(true)
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
openPurchaseStatus(row) {
|
||||||
|
this.purchaseStatusForm = { id: row.id, purchaseStatus: row.purchaseStatus, progressRemark: row.progressRemark }
|
||||||
|
this.purchaseStatusOpen = true
|
||||||
|
this.$nextTick(() => this.$refs.mobilePurchaseStatusForm && this.$refs.mobilePurchaseStatusForm.clearValidate())
|
||||||
|
},
|
||||||
|
submitPurchaseStatus() {
|
||||||
|
this.$refs.mobilePurchaseStatusForm.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
updatePurchaseStatus(this.purchaseStatusForm.id, this.purchaseStatusForm).then(() => {
|
||||||
|
this.$modal.msgSuccess('采购状态已更新')
|
||||||
|
this.purchaseStatusOpen = false
|
||||||
|
this.loadPurchases(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openPurchaseArrival(row) {
|
||||||
|
this.purchaseArrivalOpen = true
|
||||||
|
this.purchaseArrivalRequest = Object.assign({}, row)
|
||||||
|
this.resetPurchaseArrivalForm()
|
||||||
|
this.refreshPurchaseArrival(row.id)
|
||||||
|
},
|
||||||
|
refreshPurchaseArrival(requestId) {
|
||||||
|
this.purchaseArrivalLoading = true
|
||||||
|
Promise.all([getPurchase(requestId), listPurchaseArrival(requestId)]).then(([requestRes, arrivalRes]) => {
|
||||||
|
this.purchaseArrivalRequest = requestRes.data
|
||||||
|
this.purchaseArrivalList = arrivalRes.data || []
|
||||||
|
this.resetPurchaseArrivalForm()
|
||||||
|
}).finally(() => { this.purchaseArrivalLoading = false })
|
||||||
|
},
|
||||||
|
resetPurchaseArrivalForm() {
|
||||||
|
this.purchaseArrivalForm = {
|
||||||
|
purchaseRequestId: this.purchaseArrivalRequest && this.purchaseArrivalRequest.id,
|
||||||
|
arrivalQuantity: undefined,
|
||||||
|
actualUnitPrice: this.purchaseArrivalRequest && this.purchaseArrivalRequest.estimatedUnitPrice,
|
||||||
|
arrivalDate: this.parseTime(new Date(), '{y}-{m}-{d}'),
|
||||||
|
remark: undefined
|
||||||
|
}
|
||||||
|
this.$nextTick(() => this.$refs.mobileArrivalForm && this.$refs.mobileArrivalForm.clearValidate())
|
||||||
|
},
|
||||||
|
submitPurchaseArrival() {
|
||||||
|
if (!this.purchaseArrivalRequest || !this.$refs.mobileArrivalForm) return
|
||||||
|
this.purchaseArrivalForm.purchaseRequestId = this.purchaseArrivalRequest.id
|
||||||
|
this.$refs.mobileArrivalForm.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
addPurchaseArrival(this.purchaseArrivalForm).then(() => {
|
||||||
|
this.$modal.msgSuccess('到货已登记,等待验收')
|
||||||
|
this.refreshPurchaseArrival(this.purchaseArrivalRequest.id)
|
||||||
|
this.loadPurchases(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
acceptMobileArrival(row, status) {
|
||||||
|
if (status === 1) {
|
||||||
|
this.$modal.confirm('确认数量、单价和物料无误并通过验收?').then(() => acceptPurchaseArrival(row.id, { acceptanceStatus: 1 })).then(() => {
|
||||||
|
this.$modal.msgSuccess('验收通过,已生成待入库记录')
|
||||||
|
this.refreshPurchaseArrival(this.purchaseArrivalRequest.id)
|
||||||
|
this.loadPurchases(true)
|
||||||
|
this.loadSummary()
|
||||||
|
}).catch(() => {})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$prompt('请输入拒收原因', '拒收到货', { inputValidator: value => !!(value && value.trim()) || '拒收原因不能为空' }).then(({ value }) => {
|
||||||
|
return acceptPurchaseArrival(row.id, { acceptanceStatus: 2, acceptanceRemark: value })
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess('已拒收,本次数量可以重新登记')
|
||||||
|
this.refreshPurchaseArrival(this.purchaseArrivalRequest.id)
|
||||||
|
this.loadPurchases(true)
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
deleteMobileArrival(row) {
|
||||||
|
this.$modal.confirm('确认删除待验收到货记录“' + row.arrivalNo + '”?').then(() => delPurchaseArrival(row.id)).then(() => {
|
||||||
|
this.$modal.msgSuccess('到货记录已删除')
|
||||||
|
this.refreshPurchaseArrival(this.purchaseArrivalRequest.id)
|
||||||
|
this.loadPurchases(true)
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
revokeMobileArrival(row) {
|
||||||
|
const action = Number(row.acceptanceStatus) === 1 ? '验收通过' : '拒收'
|
||||||
|
this.$prompt('请输入撤销原因', '撤销' + action, { inputValidator: value => !!(value && value.trim()) || '撤销原因不能为空' }).then(({ value }) => {
|
||||||
|
return revokePurchaseArrival(row.id, { revokeRemark: value })
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess('验收已撤销')
|
||||||
|
this.refreshPurchaseArrival(this.purchaseArrivalRequest.id)
|
||||||
|
this.loadPurchases(true)
|
||||||
|
this.loadSummary()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
analyzeAgentText() {
|
analyzeAgentText() {
|
||||||
const text = (this.agent.text || '').trim()
|
const text = (this.agent.text || '').trim()
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@ -688,14 +1068,25 @@ export default {
|
|||||||
if (!this.agent.plan || !item) {
|
if (!this.agent.plan || !item) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const plan = this.completePlanWithResult(this.agent.plan, item)
|
const text = this.selectionInstruction(this.agent.plan, item)
|
||||||
this.agent.plan = plan
|
this.agent.loading = true
|
||||||
this.agent.contextResults = [item]
|
analyzeAgent({ text, contextResults: [item] }).then(res => {
|
||||||
if (plan.executable) {
|
this.agent.plan = res.data
|
||||||
this.autoFillAgentForm(plan)
|
this.agent.contextResults = [item]
|
||||||
} else {
|
if (res.data.executable) this.autoFillAgentForm(res.data)
|
||||||
this.$modal.msgWarning(plan.message || '已选择产品,请补充缺少的信息')
|
else this.$modal.msgWarning(res.data.message || '已选择产品,请补充缺少的信息')
|
||||||
}
|
}).finally(() => { this.agent.loading = false })
|
||||||
|
},
|
||||||
|
selectionInstruction(plan, item) {
|
||||||
|
const quantity = plan.quantity || 1
|
||||||
|
const unit = item.unit || plan.unit || '个'
|
||||||
|
const project = plan.action === 'create_in_record'
|
||||||
|
? (plan.sourceProjectName ? `,来源项目${plan.sourceProjectName}` : '') + (plan.expenseCategoryName ? `,经费科目${plan.expenseCategoryName}` : '')
|
||||||
|
: (plan.usageProjectName ? `,使用项目${plan.usageProjectName}` : '')
|
||||||
|
const operator = plan.operatorName ? `,操作人${plan.operatorName}` : ''
|
||||||
|
if (plan.action === 'create_in_record') return `第一个入库${quantity}${unit}${project}${operator}`
|
||||||
|
if (plan.action === 'create_stock_check') return `盘点第一个实际${quantity}${unit}${operator}`
|
||||||
|
return `第一个出库${quantity}${unit}${project}${operator}`
|
||||||
},
|
},
|
||||||
completePlanWithResult(source, item) {
|
completePlanWithResult(source, item) {
|
||||||
const plan = Object.assign({}, source, {
|
const plan = Object.assign({}, source, {
|
||||||
@ -753,10 +1144,12 @@ export default {
|
|||||||
category: plan.category,
|
category: plan.category,
|
||||||
spec: plan.spec,
|
spec: plan.spec,
|
||||||
unit: plan.unit,
|
unit: plan.unit,
|
||||||
|
usageProjectId: plan.usageProjectId,
|
||||||
outOperator: plan.operatorName,
|
outOperator: plan.operatorName,
|
||||||
outType: plan.outType || 1,
|
outType: plan.outType || 1,
|
||||||
outNum: plan.quantity,
|
outNum: plan.quantity,
|
||||||
sourceNo: undefined,
|
sourceNo: undefined,
|
||||||
|
crossProjectReason: plan.crossProjectReason,
|
||||||
remark: plan.remark
|
remark: plan.remark
|
||||||
}
|
}
|
||||||
this.setOutRules()
|
this.setOutRules()
|
||||||
@ -766,10 +1159,12 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
areaId: plan.areaId,
|
areaId: plan.areaId,
|
||||||
productId: plan.productId,
|
productId: plan.productId,
|
||||||
|
usageProjectId: plan.usageProjectId,
|
||||||
outOperator: plan.operatorName,
|
outOperator: plan.operatorName,
|
||||||
outType: plan.outType || 1,
|
outType: plan.outType || 1,
|
||||||
outNum: plan.quantity,
|
outNum: plan.quantity,
|
||||||
sourceNo: undefined,
|
sourceNo: undefined,
|
||||||
|
crossProjectReason: plan.crossProjectReason,
|
||||||
remark: plan.remark
|
remark: plan.remark
|
||||||
}
|
}
|
||||||
this.setOutRules()
|
this.setOutRules()
|
||||||
@ -779,6 +1174,8 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
areaId: plan.areaId,
|
areaId: plan.areaId,
|
||||||
productId: plan.productId,
|
productId: plan.productId,
|
||||||
|
sourceProjectId: plan.sourceProjectId,
|
||||||
|
expenseCategoryId: plan.expenseCategoryId,
|
||||||
inOperator: plan.operatorName,
|
inOperator: plan.operatorName,
|
||||||
inType: plan.inType || 2,
|
inType: plan.inType || 2,
|
||||||
inNum: plan.quantity,
|
inNum: plan.quantity,
|
||||||
@ -845,6 +1242,7 @@ export default {
|
|||||||
actionLabel(action) {
|
actionLabel(action) {
|
||||||
const map = {
|
const map = {
|
||||||
query_stock: '查库存',
|
query_stock: '查库存',
|
||||||
|
query_purchase: '查采购状态',
|
||||||
create_in_record: '入库',
|
create_in_record: '入库',
|
||||||
confirm_in_record: '确认入库',
|
confirm_in_record: '确认入库',
|
||||||
create_out_record: '出库',
|
create_out_record: '出库',
|
||||||
@ -858,19 +1256,22 @@ export default {
|
|||||||
optionselectArea().then(res => { this.areaOptions = res.data || [] })
|
optionselectArea().then(res => { this.areaOptions = res.data || [] })
|
||||||
optionselectProduct().then(res => { this.productOptions = res.data || [] })
|
optionselectProduct().then(res => { this.productOptions = res.data || [] })
|
||||||
optionselectUser().then(res => { this.userOptions = res.data || [] })
|
optionselectUser().then(res => { this.userOptions = res.data || [] })
|
||||||
|
projectOptions('').then(res => { this.projectOptionList = res.data || [] })
|
||||||
|
expenseCategoryOptionApi().then(res => { this.expenseCategoryOptions = res.data || [] })
|
||||||
},
|
},
|
||||||
refreshAll() {
|
refreshAll() {
|
||||||
this.loadStock()
|
if (this.hasAnyPermission(['warehouse:stock:list'])) this.loadStock()
|
||||||
this.loadInRecords()
|
if (this.hasAnyPermission(['warehouse:in:list'])) this.loadInRecords()
|
||||||
this.loadOutRecords()
|
if (this.hasAnyPermission(['warehouse:out:list'])) this.loadOutRecords()
|
||||||
this.loadCheckRecords()
|
if (this.hasAnyPermission(['warehouse:check:list'])) this.loadCheckRecords()
|
||||||
|
if (this.canViewPurchase) this.loadPurchases(true)
|
||||||
this.loadSummary()
|
this.loadSummary()
|
||||||
},
|
},
|
||||||
loadSummary() {
|
loadSummary() {
|
||||||
listStock({ pageNum: 1, pageSize: 1 }).then(res => { this.summary.stockTotal = res.total || 0 })
|
if (this.hasAnyPermission(['warehouse:stock:list'])) listStock({ pageNum: 1, pageSize: 1 }).then(res => { this.summary.stockTotal = res.total || 0 })
|
||||||
listInRecord({ pageNum: 1, pageSize: 1, inStatus: 0 }).then(res => { this.summary.pendingIn = res.total || 0 })
|
if (this.hasAnyPermission(['warehouse:in:list'])) listInRecord({ pageNum: 1, pageSize: 1, inStatus: 0 }).then(res => { this.summary.pendingIn = res.total || 0 })
|
||||||
listStockCheck({ pageNum: 1, pageSize: 1, checkStatus: 0 }).then(res => { this.summary.pendingCheck = res.total || 0 })
|
if (this.hasAnyPermission(['warehouse:check:list'])) listStockCheck({ pageNum: 1, pageSize: 1, checkStatus: 0 }).then(res => { this.summary.pendingCheck = res.total || 0 })
|
||||||
listOutRecord({ pageNum: 1, pageSize: 1 }).then(res => { this.summary.recentOut = res.total || 0 })
|
if (this.hasAnyPermission(['warehouse:out:list'])) listOutRecord({ pageNum: 1, pageSize: 1 }).then(res => { this.summary.recentOut = res.total || 0 })
|
||||||
},
|
},
|
||||||
loadStock() {
|
loadStock() {
|
||||||
listStock(this.stockQuery).then(res => { this.stockList = res.rows || [] })
|
listStock(this.stockQuery).then(res => { this.stockList = res.rows || [] })
|
||||||
@ -1043,6 +1444,7 @@ export default {
|
|||||||
switchTab(tab) {
|
switchTab(tab) {
|
||||||
this.activeTab = tab
|
this.activeTab = tab
|
||||||
if (tab === 'stock') this.loadStock()
|
if (tab === 'stock') this.loadStock()
|
||||||
|
if (tab === 'purchase') this.loadPurchases(true)
|
||||||
if (tab === 'in') this.loadInRecords()
|
if (tab === 'in') this.loadInRecords()
|
||||||
if (tab === 'out') this.loadOutRecords()
|
if (tab === 'out') this.loadOutRecords()
|
||||||
if (tab === 'check') this.loadCheckRecords()
|
if (tab === 'check') this.loadCheckRecords()
|
||||||
@ -1059,11 +1461,12 @@ export default {
|
|||||||
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
||||||
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
||||||
},
|
},
|
||||||
|
projectLabel(project) { return project.projectName + '(' + project.projectCode + ')' },
|
||||||
userLabel(user) {
|
userLabel(user) {
|
||||||
return user.nickName || user.userName
|
return user.nickName || user.userName
|
||||||
},
|
},
|
||||||
userValue(user) {
|
userValue(user) {
|
||||||
return user.nickName || user.userName
|
return user.userName
|
||||||
},
|
},
|
||||||
productMeta(row) {
|
productMeta(row) {
|
||||||
return [row.brand && '品牌:' + row.brand, row.category && '类别:' + row.category, row.spec && '规格:' + row.spec, row.unit && '单位:' + row.unit].filter(Boolean).join(' ')
|
return [row.brand && '品牌:' + row.brand, row.category && '类别:' + row.category, row.spec && '规格:' + row.spec, row.unit && '单位:' + row.unit].filter(Boolean).join(' ')
|
||||||
@ -1082,6 +1485,8 @@ export default {
|
|||||||
inNo: undefined,
|
inNo: undefined,
|
||||||
areaId: undefined,
|
areaId: undefined,
|
||||||
productId: undefined,
|
productId: undefined,
|
||||||
|
sourceProjectId: undefined,
|
||||||
|
expenseCategoryId: undefined,
|
||||||
inOperator: undefined,
|
inOperator: undefined,
|
||||||
inType: 2,
|
inType: 2,
|
||||||
inNum: undefined,
|
inNum: undefined,
|
||||||
@ -1100,6 +1505,8 @@ export default {
|
|||||||
this.formRules = {
|
this.formRules = {
|
||||||
areaId: [{ required: true, message: '请选择库区', trigger: 'change' }],
|
areaId: [{ required: true, message: '请选择库区', trigger: 'change' }],
|
||||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||||
|
sourceProjectId: [{ required: true, message: '请选择来源项目', trigger: 'change' }],
|
||||||
|
expenseCategoryId: [{ required: true, message: '请选择经费科目', trigger: 'change' }],
|
||||||
inOperator: [{ required: true, message: '请选择入库人', trigger: 'change' }],
|
inOperator: [{ required: true, message: '请选择入库人', trigger: 'change' }],
|
||||||
inType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
inType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||||
inNum: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
inNum: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
||||||
@ -1108,7 +1515,7 @@ export default {
|
|||||||
openOutForm() {
|
openOutForm() {
|
||||||
this.dialogType = 'out'
|
this.dialogType = 'out'
|
||||||
this.dialogTitle = '出库登记'
|
this.dialogTitle = '出库登记'
|
||||||
this.form = { areaId: undefined, productId: undefined, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, remark: undefined }
|
this.form = { areaId: undefined, productId: undefined, usageProjectId: undefined, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, crossProjectReason: undefined, remark: undefined }
|
||||||
this.setOutRules()
|
this.setOutRules()
|
||||||
this.dialogOpen = true
|
this.dialogOpen = true
|
||||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||||
@ -1116,7 +1523,7 @@ export default {
|
|||||||
openStockOutForm(row) {
|
openStockOutForm(row) {
|
||||||
this.dialogType = 'stockOut'
|
this.dialogType = 'stockOut'
|
||||||
this.dialogTitle = '库存出库'
|
this.dialogTitle = '库存出库'
|
||||||
this.form = { id: row.id, productName: row.productName, brand: row.brand, category: row.category, spec: row.spec, unit: row.unit, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, remark: undefined }
|
this.form = { id: row.id, productName: row.productName, brand: row.brand, category: row.category, spec: row.spec, unit: row.unit, usageProjectId: undefined, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, crossProjectReason: undefined, remark: undefined }
|
||||||
this.setOutRules()
|
this.setOutRules()
|
||||||
this.dialogOpen = true
|
this.dialogOpen = true
|
||||||
this.$nextTick(() => this.resetForm('mobileForm'))
|
this.$nextTick(() => this.resetForm('mobileForm'))
|
||||||
@ -1125,6 +1532,7 @@ export default {
|
|||||||
this.formRules = {
|
this.formRules = {
|
||||||
areaId: [{ required: this.dialogType === 'out', message: '请选择库区', trigger: 'change' }],
|
areaId: [{ required: this.dialogType === 'out', message: '请选择库区', trigger: 'change' }],
|
||||||
productId: [{ required: this.dialogType === 'out', message: '请选择产品', trigger: 'change' }],
|
productId: [{ required: this.dialogType === 'out', message: '请选择产品', trigger: 'change' }],
|
||||||
|
usageProjectId: [{ required: true, message: '请选择使用项目', trigger: 'change' }],
|
||||||
outOperator: [{ required: true, message: '请选择出库人', trigger: 'change' }],
|
outOperator: [{ required: true, message: '请选择出库人', trigger: 'change' }],
|
||||||
outType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
outType: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||||
outNum: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
outNum: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
||||||
@ -1247,6 +1655,7 @@ export default {
|
|||||||
if (this.dialogType === 'out') request = addOutRecord(this.form)
|
if (this.dialogType === 'out') request = addOutRecord(this.form)
|
||||||
if (this.dialogType === 'stockOut') request = outStock(this.form)
|
if (this.dialogType === 'stockOut') request = outStock(this.form)
|
||||||
if (this.dialogType === 'check') request = addStockCheck(this.form)
|
if (this.dialogType === 'check') request = addStockCheck(this.form)
|
||||||
|
if (this.dialogType === 'purchase') request = this.form.id ? updatePurchase(this.form) : addPurchase(this.form)
|
||||||
if (this.dialogType === 'product') request = this.form.id ? updateProduct(this.form) : addProduct(this.form)
|
if (this.dialogType === 'product') request = this.form.id ? updateProduct(this.form) : addProduct(this.form)
|
||||||
if (this.dialogType === 'area') request = this.form.id ? updateArea(this.form) : addArea(this.form)
|
if (this.dialogType === 'area') request = this.form.id ? updateArea(this.form) : addArea(this.form)
|
||||||
request.then(() => {
|
request.then(() => {
|
||||||
@ -1254,6 +1663,11 @@ export default {
|
|||||||
const hasPendingNextPlan = !!this.agent.pendingNextPlan
|
const hasPendingNextPlan = !!this.agent.pendingNextPlan
|
||||||
this.$modal.msgSuccess(editMaintainData ? '修改成功' : '保存成功')
|
this.$modal.msgSuccess(editMaintainData ? '修改成功' : '保存成功')
|
||||||
this.dialogOpen = false
|
this.dialogOpen = false
|
||||||
|
if (this.dialogType === 'purchase') {
|
||||||
|
this.loadPurchases(true)
|
||||||
|
this.loadOptions()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (this.dialogType === 'product') {
|
if (this.dialogType === 'product') {
|
||||||
this.loadProducts()
|
this.loadProducts()
|
||||||
this.loadOptions()
|
this.loadOptions()
|
||||||
@ -1626,6 +2040,79 @@ export default {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.purchase-project {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.purchase-progress {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.purchase-progress span {
|
||||||
|
padding: 4px 7px;
|
||||||
|
color: #606266;
|
||||||
|
background: #f2f5f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.purchase-progress .pending {
|
||||||
|
color: #b26a00;
|
||||||
|
background: #fdf6ec;
|
||||||
|
}
|
||||||
|
.purchase-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
.purchase-actions .el-button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.mobile-arrival-head {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f7f9fc;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
.mobile-arrival-head strong,
|
||||||
|
.mobile-arrival-head > span {
|
||||||
|
display: block;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
.mobile-arrival-head > span {
|
||||||
|
color: #7a8794;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.mobile-arrival-form {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px 10px 2px;
|
||||||
|
border: 1px solid #dfe7f1;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
.mobile-arrival-list {
|
||||||
|
min-height: 80px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.arrival-card {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #e4e9f0;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
.arrival-notes {
|
||||||
|
margin-top: 7px;
|
||||||
|
padding: 7px;
|
||||||
|
color: #606266;
|
||||||
|
background: #f7f9fc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
.record-line,
|
.record-line,
|
||||||
.check-grid {
|
.check-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1655,17 +2142,17 @@ export default {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
||||||
height: 62px;
|
height: 62px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-top: 1px solid #e6ebf2;
|
border-top: 1px solid #e6ebf2;
|
||||||
}
|
}
|
||||||
.bottom-nav button {
|
.bottom-nav button {
|
||||||
|
flex: 1;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #7a8794;
|
color: #7a8794;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
.bottom-nav i {
|
.bottom-nav i {
|
||||||
display: block;
|
display: block;
|
||||||
@ -1705,7 +2192,12 @@ export default {
|
|||||||
margin-top: 6vh !important;
|
margin-top: 6vh !important;
|
||||||
}
|
}
|
||||||
.mobile-dialog .el-dialog__body {
|
.mobile-dialog .el-dialog__body {
|
||||||
|
max-height: 72vh;
|
||||||
padding: 12px 16px 0;
|
padding: 12px 16px 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.mobile-arrival-dialog .el-dialog__body {
|
||||||
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.el-message-box__wrapper {
|
.el-message-box__wrapper {
|
||||||
|
|||||||
17
ruoyi-ui/src/views/monitor/cache/index.vue
vendored
17
ruoyi-ui/src/views/monitor/cache/index.vue
vendored
@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getCache } from "@/api/monitor/cache"
|
import { getCache } from "@/api/monitor/cache"
|
||||||
import * as echarts from "echarts"
|
import echarts from "@/utils/echarts"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Cache",
|
name: "Cache",
|
||||||
@ -76,6 +76,7 @@ export default {
|
|||||||
commandstats: null,
|
commandstats: null,
|
||||||
// 使用内存
|
// 使用内存
|
||||||
usedmemory: null,
|
usedmemory: null,
|
||||||
|
resizeHandler: null,
|
||||||
// cache信息
|
// cache信息
|
||||||
cache: []
|
cache: []
|
||||||
}
|
}
|
||||||
@ -84,6 +85,11 @@ export default {
|
|||||||
this.getList()
|
this.getList()
|
||||||
this.openLoading()
|
this.openLoading()
|
||||||
},
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.resizeHandler) window.removeEventListener("resize", this.resizeHandler)
|
||||||
|
if (this.commandstats) this.commandstats.dispose()
|
||||||
|
if (this.usedmemory) this.usedmemory.dispose()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查缓存询信息 */
|
/** 查缓存询信息 */
|
||||||
getList() {
|
getList() {
|
||||||
@ -91,7 +97,7 @@ export default {
|
|||||||
this.cache = response.data
|
this.cache = response.data
|
||||||
this.$modal.closeLoading()
|
this.$modal.closeLoading()
|
||||||
|
|
||||||
this.commandstats = echarts.init(this.$refs.commandstats, "macarons")
|
this.commandstats = echarts.init(this.$refs.commandstats)
|
||||||
this.commandstats.setOption({
|
this.commandstats.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
@ -110,7 +116,7 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons")
|
this.usedmemory = echarts.init(this.$refs.usedmemory)
|
||||||
this.usedmemory.setOption({
|
this.usedmemory.setOption({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
|
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
|
||||||
@ -133,10 +139,11 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
window.addEventListener("resize", () => {
|
this.resizeHandler = () => {
|
||||||
this.commandstats.resize()
|
this.commandstats.resize()
|
||||||
this.usedmemory.resize()
|
this.usedmemory.resize()
|
||||||
})
|
}
|
||||||
|
window.addEventListener("resize", this.resizeHandler)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 打开加载层
|
// 打开加载层
|
||||||
|
|||||||
185
ruoyi-ui/src/views/project/info/index.vue
Normal file
185
ruoyi-ui/src/views/project/info/index.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" v-show="showSearch">
|
||||||
|
<el-form-item label="项目编码" prop="projectCode">
|
||||||
|
<el-input v-model="queryParams.projectCode" clearable placeholder="请输入项目编码" @keyup.enter.native="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目名称" prop="projectName">
|
||||||
|
<el-input v-model="queryParams.projectName" clearable placeholder="请输入项目名称" @keyup.enter.native="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="projectStatus">
|
||||||
|
<el-select v-model="queryParams.projectStatus" clearable placeholder="全部状态">
|
||||||
|
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5"><el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['project:project:add']">新增项目</el-button></el-col>
|
||||||
|
<el-col :span="1.5"><el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate" v-hasPermi="['project:project:edit']">修改</el-button></el-col>
|
||||||
|
<el-col :span="1.5"><el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="single" @click="handleDelete" v-hasPermi="['project:project:remove']">删除</el-button></el-col>
|
||||||
|
<el-col :span="1.5"><el-button type="warning" plain icon="el-icon-coin" size="mini" @click="openCategories" v-hasPermi="['project:category:edit']">经费科目</el-button></el-col>
|
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="projectList" @selection-change="handleSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column label="项目编码" prop="projectCode" min-width="120" />
|
||||||
|
<el-table-column label="项目名称" prop="projectName" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column label="负责人" prop="managerName" min-width="100" />
|
||||||
|
<el-table-column label="归属部门" prop="deptName" min-width="120" />
|
||||||
|
<el-table-column label="状态" align="center" width="90">
|
||||||
|
<template slot-scope="scope"><el-tag :type="statusType(scope.row.projectStatus)" size="mini">{{ statusLabel(scope.row.projectStatus) }}</el-tag></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="预算" prop="budgetAmount" align="right" min-width="110"><template slot-scope="scope">{{ money(scope.row.budgetAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="已占用" prop="committedAmount" align="right" min-width="110"><template slot-scope="scope">{{ money(scope.row.committedAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="已支出" prop="spentAmount" align="right" min-width="110"><template slot-scope="scope">{{ money(scope.row.spentAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="可用" prop="availableAmount" align="right" min-width="110"><template slot-scope="scope"><span :class="{ danger: Number(scope.row.availableAmount) < 0 }">{{ money(scope.row.availableAmount) }}</span></template></el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="230" fixed="right">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-coin" @click="openBudget(scope.row)" v-hasPermi="['project:budget:list', 'project:budget:edit', 'project:project:manage']">经费</el-button>
|
||||||
|
<el-button v-if="scope.row.projectCode !== 'LEGACY'" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:project:edit']">修改</el-button>
|
||||||
|
<el-button v-if="scope.row.projectCode !== 'LEGACY'" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['project:project:remove']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||||
|
|
||||||
|
<el-dialog :title="title" :visible.sync="open" width="720px" append-to-body>
|
||||||
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12"><el-form-item label="项目编码" prop="projectCode"><el-input v-model="form.projectCode" placeholder="字母、数字、下划线或中划线" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="项目名称" prop="projectName"><el-input v-model="form.projectName" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="负责人" prop="managerId"><el-select v-model="form.managerId" filterable clearable style="width:100%"><el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="item.userId" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="项目状态" prop="projectStatus"><el-select v-model="form.projectStatus" style="width:100%"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="开始日期" prop="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="结束日期" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="项目成员" prop="memberIds"><el-select v-model="form.memberIds" multiple filterable collapse-tags style="width:100%"><el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="item.userId" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer"><el-button type="primary" @click="submitForm">确定</el-button><el-button @click="open=false">取消</el-button></div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog :title="budgetTitle" :visible.sync="budgetOpen" width="980px" append-to-body>
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5"><el-button type="primary" plain size="mini" icon="el-icon-plus" @click="openBudgetEdit()" v-hasPermi="['project:budget:edit', 'project:project:manage']">配置科目预算</el-button></el-col>
|
||||||
|
<el-col :span="1.5"><el-button type="info" plain size="mini" icon="el-icon-notebook-2" @click="openLedger">查看流水</el-button></el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-table :data="budgetList" border>
|
||||||
|
<el-table-column label="经费科目" prop="categoryName" min-width="130"><template slot-scope="scope">{{ scope.row.categoryName }}({{ scope.row.categoryCode }})</template></el-table-column>
|
||||||
|
<el-table-column label="预算" align="right" prop="budgetAmount"><template slot-scope="scope">{{ money(scope.row.budgetAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="已占用" align="right" prop="committedAmount"><template slot-scope="scope">{{ money(scope.row.committedAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="已支出" align="right" prop="spentAmount"><template slot-scope="scope">{{ money(scope.row.spentAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="可用" align="right" prop="availableAmount"><template slot-scope="scope">{{ money(scope.row.availableAmount) }}</template></el-table-column>
|
||||||
|
<el-table-column label="预警线" align="center" width="90"><template slot-scope="scope">{{ scope.row.alertPercent }}%</template></el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="80"><template slot-scope="scope"><el-button type="text" @click="openBudgetEdit(scope.row)" v-hasPermi="['project:budget:edit', 'project:project:manage']">调整</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="配置项目经费" :visible.sync="budgetEditOpen" width="480px" append-to-body>
|
||||||
|
<el-form ref="budgetForm" :model="budgetForm" :rules="budgetRules" label-width="100px">
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId"><el-select v-model="budgetForm.expenseCategoryId" :disabled="!!budgetForm.id" style="width:100%"><el-option v-for="item in categoryOptionList" :key="item.id" :label="item.categoryName + '(' + item.categoryCode + ')'" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="预算金额" prop="budgetAmount"><el-input-number v-model="budgetForm.budgetAmount" :min="0" :precision="2" :controls="false" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="预警比例" prop="alertPercent"><el-input-number v-model="budgetForm.alertPercent" :min="0" :max="100" :precision="2" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="调整说明" prop="remark"><el-input v-model="budgetForm.remark" type="textarea" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer"><el-button type="primary" @click="submitBudget">保存</el-button><el-button @click="budgetEditOpen=false">取消</el-button></div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="经费流水" :visible.sync="ledgerOpen" width="1100px" append-to-body>
|
||||||
|
<el-table :data="ledgerList" border max-height="500">
|
||||||
|
<el-table-column label="时间" prop="createTime" width="165" />
|
||||||
|
<el-table-column label="科目" prop="categoryName" min-width="100" />
|
||||||
|
<el-table-column label="业务类型" prop="bizType" min-width="120" />
|
||||||
|
<el-table-column label="业务单号" prop="bizNo" min-width="150" />
|
||||||
|
<el-table-column label="预算变化" prop="budgetDelta" align="right" />
|
||||||
|
<el-table-column label="占用变化" prop="committedDelta" align="right" />
|
||||||
|
<el-table-column label="支出变化" prop="spentDelta" align="right" />
|
||||||
|
<el-table-column label="操作人" prop="operator" width="100" />
|
||||||
|
<el-table-column label="说明" prop="remark" min-width="160" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
<pagination v-show="ledgerTotal > 0" :total="ledgerTotal" :page.sync="ledgerQuery.pageNum" :limit.sync="ledgerQuery.pageSize" @pagination="getLedger" />
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="经费科目维护" :visible.sync="categoryOpen" width="760px" append-to-body>
|
||||||
|
<el-button type="primary" plain size="mini" icon="el-icon-plus" class="mb8" @click="editCategory()">新增科目</el-button>
|
||||||
|
<el-table :data="categoryList" border>
|
||||||
|
<el-table-column label="编码" prop="categoryCode" />
|
||||||
|
<el-table-column label="名称" prop="categoryName" />
|
||||||
|
<el-table-column label="排序" prop="sortOrder" width="80" />
|
||||||
|
<el-table-column label="状态" width="80"><template slot-scope="scope"><el-tag :type="scope.row.status === 0 ? 'success' : 'info'">{{ scope.row.status === 0 ? '正常' : '停用' }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="操作" width="130"><template slot-scope="scope"><el-button v-if="scope.row.categoryCode !== 'LEGACY'" type="text" @click="editCategory(scope.row)">修改</el-button><el-button v-if="scope.row.categoryCode !== 'LEGACY'" type="text" @click="removeCategory(scope.row)">删除</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog :title="categoryForm.id ? '修改经费科目' : '新增经费科目'" :visible.sync="categoryEditOpen" width="460px" append-to-body>
|
||||||
|
<el-form ref="categoryForm" :model="categoryForm" :rules="categoryRules" label-width="90px">
|
||||||
|
<el-form-item label="科目编码" prop="categoryCode"><el-input v-model="categoryForm.categoryCode" :disabled="!!categoryForm.id" /></el-form-item>
|
||||||
|
<el-form-item label="科目名称" prop="categoryName"><el-input v-model="categoryForm.categoryName" /></el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sortOrder"><el-input-number v-model="categoryForm.sortOrder" :min="0" /></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-radio-group v-model="categoryForm.status"><el-radio :label="0">正常</el-radio><el-radio :label="1">停用</el-radio></el-radio-group></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="categoryForm.remark" type="textarea" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer"><el-button type="primary" @click="submitCategory">保存</el-button><el-button @click="categoryEditOpen=false">取消</el-button></div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listProject, getProject, addProject, updateProject, delProject, listProjectBudget, saveProjectBudget, listFundLedger, listCategory, categoryOptions, addCategory, updateCategory, delCategory } from '@/api/project/project'
|
||||||
|
import { optionselectUser } from '@/api/warehouse/user'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProjectInfo',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false, showSearch: true, total: 0, projectList: [], ids: [], single: true,
|
||||||
|
queryParams: { pageNum: 1, pageSize: 10, projectCode: undefined, projectName: undefined, projectStatus: undefined },
|
||||||
|
statusOptions: [{ value: 0, label: '筹备' }, { value: 1, label: '进行中' }, { value: 2, label: '已完成' }, { value: 3, label: '已归档' }],
|
||||||
|
userOptions: [], open: false, title: '', form: {},
|
||||||
|
rules: { projectCode: [{ required: true, message: '项目编码不能为空', trigger: 'blur' }], projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }] },
|
||||||
|
budgetOpen: false, budgetTitle: '', currentProject: null, budgetList: [], budgetEditOpen: false, budgetForm: {}, categoryOptionList: [],
|
||||||
|
budgetRules: { expenseCategoryId: [{ required: true, message: '请选择经费科目', trigger: 'change' }], budgetAmount: [{ required: true, message: '预算金额不能为空', trigger: 'blur' }] },
|
||||||
|
ledgerOpen: false, ledgerList: [], ledgerTotal: 0, ledgerQuery: { pageNum: 1, pageSize: 10, projectId: undefined },
|
||||||
|
categoryOpen: false, categoryEditOpen: false, categoryList: [], categoryForm: {},
|
||||||
|
categoryRules: { categoryCode: [{ required: true, message: '编码不能为空', trigger: 'blur' }], categoryName: [{ required: true, message: '名称不能为空', trigger: 'blur' }] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() { this.getList(); optionselectUser().then(res => { this.userOptions = res.data || [] }) },
|
||||||
|
methods: {
|
||||||
|
getList() { this.loading = true; listProject(this.queryParams).then(res => { this.projectList = res.rows; this.total = res.total }).finally(() => { this.loading = false }) },
|
||||||
|
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
|
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
|
||||||
|
handleSelectionChange(rows) { this.ids = rows.map(item => item.id); this.single = rows.length !== 1 },
|
||||||
|
reset() { this.form = { id: undefined, projectCode: undefined, projectName: undefined, managerId: undefined, startDate: undefined, endDate: undefined, projectStatus: 1, currency: 'CNY', memberIds: [], remark: undefined }; this.resetForm('form') },
|
||||||
|
handleAdd() { this.reset(); this.title = '新增项目'; this.open = true },
|
||||||
|
handleUpdate(row) { const id = row.id || this.ids[0]; getProject(id).then(res => { this.form = res.data; this.form.memberIds = this.form.memberIds || []; this.title = '修改项目'; this.open = true }) },
|
||||||
|
submitForm() { this.$refs.form.validate(valid => { if (!valid) return; (this.form.id ? updateProject(this.form) : addProject(this.form)).then(() => { this.$modal.msgSuccess(this.form.id ? '修改成功' : '新增成功'); this.open = false; this.getList() }) }) },
|
||||||
|
handleDelete(row) { const id = row.id || this.ids[0]; const item = row.id ? row : this.projectList.find(p => p.id === id); this.$modal.confirm('确认删除项目“' + (item ? item.projectName : id) + '”?').then(() => delProject(id)).then(() => { this.$modal.msgSuccess('删除成功'); this.getList() }).catch(() => {}) },
|
||||||
|
openBudget(row) { this.currentProject = row; this.budgetTitle = `项目经费:${row.projectName}`; this.budgetOpen = true; this.loadBudget(); categoryOptions().then(res => { this.categoryOptionList = res.data || [] }) },
|
||||||
|
loadBudget() { listProjectBudget(this.currentProject.id).then(res => { this.budgetList = res.data || [] }) },
|
||||||
|
openBudgetEdit(row) { this.budgetForm = row ? { ...row } : { projectId: this.currentProject.id, expenseCategoryId: undefined, budgetAmount: 0, alertPercent: 80, remark: undefined }; this.budgetEditOpen = true; this.$nextTick(() => this.$refs.budgetForm && this.$refs.budgetForm.clearValidate()) },
|
||||||
|
submitBudget() { this.budgetForm.projectId = this.currentProject.id; this.$refs.budgetForm.validate(valid => { if (!valid) return; saveProjectBudget(this.budgetForm).then(() => { this.$modal.msgSuccess('经费预算已保存'); this.budgetEditOpen = false; this.loadBudget(); this.getList() }) }) },
|
||||||
|
openLedger() { this.ledgerQuery = { pageNum: 1, pageSize: 10, projectId: this.currentProject.id }; this.ledgerOpen = true; this.getLedger() },
|
||||||
|
getLedger() { listFundLedger(this.ledgerQuery).then(res => { this.ledgerList = res.rows || []; this.ledgerTotal = res.total || 0 }) },
|
||||||
|
openCategories() { this.categoryOpen = true; this.loadCategories() },
|
||||||
|
loadCategories() { listCategory({}).then(res => { this.categoryList = res.data || [] }) },
|
||||||
|
editCategory(row) { this.categoryForm = row ? { ...row } : { id: undefined, categoryCode: undefined, categoryName: undefined, sortOrder: 0, status: 0, remark: undefined }; this.categoryEditOpen = true; this.$nextTick(() => this.$refs.categoryForm && this.$refs.categoryForm.clearValidate()) },
|
||||||
|
submitCategory() { this.$refs.categoryForm.validate(valid => { if (!valid) return; (this.categoryForm.id ? updateCategory(this.categoryForm) : addCategory(this.categoryForm)).then(() => { this.$modal.msgSuccess('经费科目已保存'); this.categoryEditOpen = false; this.loadCategories() }) }) },
|
||||||
|
removeCategory(row) { this.$modal.confirm('确认删除经费科目“' + row.categoryName + '”?').then(() => delCategory(row.id)).then(() => { this.$modal.msgSuccess('删除成功'); this.loadCategories() }).catch(() => {}) },
|
||||||
|
userLabel(user) { return (user.nickName || user.userName) + (user.userName ? `(${user.userName})` : '') },
|
||||||
|
statusLabel(value) { const item = this.statusOptions.find(i => i.value === value); return item ? item.label : '-' },
|
||||||
|
statusType(value) { return ({ 0: 'warning', 1: 'success', 2: 'info', 3: '' })[value] || '' },
|
||||||
|
money(value) { return Number(value || 0).toFixed(2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.danger { color: #f56c6c; font-weight: 600; }
|
||||||
|
</style>
|
||||||
@ -32,6 +32,11 @@
|
|||||||
<el-option v-for="dict in dict.type.wms_in_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
<el-option v-for="dict in dict.type.wms_in_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="来源项目" prop="sourceProjectId">
|
||||||
|
<el-select v-model="queryParams.sourceProjectId" placeholder="请选择来源项目" clearable filterable>
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
@ -56,6 +61,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="来源单号" align="center" prop="sourceNo" min-width="170" show-overflow-tooltip />
|
<el-table-column label="来源单号" align="center" prop="sourceNo" min-width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column label="来源项目" min-width="180" show-overflow-tooltip>
|
||||||
|
<template slot-scope="scope">{{ scope.row.sourceProjectName || '-' }}<span v-if="scope.row.expenseCategoryName"> / {{ scope.row.expenseCategoryName }}</span></template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="产品信息" min-width="280">
|
<el-table-column label="产品信息" min-width="280">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
||||||
@ -75,7 +83,7 @@
|
|||||||
<template slot-scope="scope"><dict-tag :options="dict.type.wms_in_status" :value="scope.row.inStatus" /></template>
|
<template slot-scope="scope"><dict-tag :options="dict.type.wms_in_status" :value="scope.row.inStatus" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="系统操作人" align="center" prop="operator" />
|
<el-table-column label="系统操作人" align="center" prop="operator" />
|
||||||
<el-table-column label="入库人" align="center" prop="inOperator" />
|
<el-table-column label="入库人" align="center" prop="inOperatorName" />
|
||||||
<el-table-column label="确认时间" align="center" prop="confirmTime" width="180">
|
<el-table-column label="确认时间" align="center" prop="confirmTime" width="180">
|
||||||
<template slot-scope="scope">{{ parseTime(scope.row.confirmTime) }}</template>
|
<template slot-scope="scope">{{ parseTime(scope.row.confirmTime) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -103,6 +111,16 @@
|
|||||||
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="来源项目" prop="sourceProjectId">
|
||||||
|
<el-select v-model="form.sourceProjectId" :disabled="!!form.arrivalId" placeholder="请选择物资来源项目" filterable style="width:100%">
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId">
|
||||||
|
<el-select v-model="form.expenseCategoryId" :disabled="!!form.arrivalId" placeholder="请选择经费科目" filterable style="width:100%">
|
||||||
|
<el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="入库人" prop="inOperator">
|
<el-form-item label="入库人" prop="inOperator">
|
||||||
<el-select v-model="form.inOperator" placeholder="请选择入库人" filterable>
|
<el-select v-model="form.inOperator" placeholder="请选择入库人" filterable>
|
||||||
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
||||||
@ -129,6 +147,7 @@ import { listInRecord, getInRecord, delInRecord, addInRecord, updateInRecord, co
|
|||||||
import { optionselectArea } from '@/api/warehouse/area'
|
import { optionselectArea } from '@/api/warehouse/area'
|
||||||
import { optionselectProduct } from '@/api/warehouse/product'
|
import { optionselectProduct } from '@/api/warehouse/product'
|
||||||
import { optionselectUser } from '@/api/warehouse/user'
|
import { optionselectUser } from '@/api/warehouse/user'
|
||||||
|
import { projectOptions, categoryOptions } from '@/api/project/project'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InRecord',
|
name: 'InRecord',
|
||||||
@ -145,13 +164,17 @@ export default {
|
|||||||
areaOptions: [],
|
areaOptions: [],
|
||||||
productOptions: [],
|
productOptions: [],
|
||||||
userOptions: [],
|
userOptions: [],
|
||||||
|
projectOptionList: [],
|
||||||
|
expenseCategoryOptions: [],
|
||||||
title: '',
|
title: '',
|
||||||
open: false,
|
open: false,
|
||||||
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined, brand: undefined, category: undefined, spec: undefined, inOperator: undefined, inType: undefined, inStatus: undefined },
|
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined, brand: undefined, category: undefined, spec: undefined, inOperator: undefined, inType: undefined, inStatus: undefined, sourceProjectId: undefined },
|
||||||
form: {},
|
form: {},
|
||||||
rules: {
|
rules: {
|
||||||
areaId: [{ required: true, message: '库区不能为空', trigger: 'change' }],
|
areaId: [{ required: true, message: '库区不能为空', trigger: 'change' }],
|
||||||
productId: [{ required: true, message: '产品不能为空', trigger: 'change' }],
|
productId: [{ required: true, message: '产品不能为空', trigger: 'change' }],
|
||||||
|
sourceProjectId: [{ required: true, message: '来源项目不能为空', trigger: 'change' }],
|
||||||
|
expenseCategoryId: [{ required: true, message: '经费科目不能为空', trigger: 'change' }],
|
||||||
inOperator: [{ required: true, message: '入库人不能为空', trigger: 'change' }],
|
inOperator: [{ required: true, message: '入库人不能为空', trigger: 'change' }],
|
||||||
inType: [{ required: true, message: '入库类型不能为空', trigger: 'change' }],
|
inType: [{ required: true, message: '入库类型不能为空', trigger: 'change' }],
|
||||||
inNum: [{ required: true, message: '入库数量不能为空', trigger: 'blur' }]
|
inNum: [{ required: true, message: '入库数量不能为空', trigger: 'blur' }]
|
||||||
@ -175,7 +198,7 @@ export default {
|
|||||||
return user.nickName || user.userName
|
return user.nickName || user.userName
|
||||||
},
|
},
|
||||||
userValue(user) {
|
userValue(user) {
|
||||||
return user.nickName || user.userName
|
return user.userName
|
||||||
},
|
},
|
||||||
areaLabel(area) {
|
areaLabel(area) {
|
||||||
return area.areaName || area.areaCode
|
return area.areaName || area.areaCode
|
||||||
@ -185,6 +208,7 @@ export default {
|
|||||||
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
||||||
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
||||||
},
|
},
|
||||||
|
projectLabel(project) { return project.projectName + '(' + project.projectCode + ')' },
|
||||||
getDistinctOptions(field) {
|
getDistinctOptions(field) {
|
||||||
const values = this.productOptions.map(item => item[field]).filter(item => item !== undefined && item !== null && item !== '')
|
const values = this.productOptions.map(item => item[field]).filter(item => item !== undefined && item !== null && item !== '')
|
||||||
return Array.from(new Set(values))
|
return Array.from(new Set(values))
|
||||||
@ -193,6 +217,8 @@ export default {
|
|||||||
optionselectArea().then(res => { this.areaOptions = res.data })
|
optionselectArea().then(res => { this.areaOptions = res.data })
|
||||||
optionselectProduct().then(res => { this.productOptions = res.data })
|
optionselectProduct().then(res => { this.productOptions = res.data })
|
||||||
optionselectUser().then(res => { this.userOptions = res.data })
|
optionselectUser().then(res => { this.userOptions = res.data })
|
||||||
|
projectOptions('').then(res => { this.projectOptionList = res.data || [] })
|
||||||
|
categoryOptions().then(res => { this.expenseCategoryOptions = res.data || [] })
|
||||||
},
|
},
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
@ -204,7 +230,7 @@ export default {
|
|||||||
},
|
},
|
||||||
cancel() { this.open = false; this.reset() },
|
cancel() { this.open = false; this.reset() },
|
||||||
reset() {
|
reset() {
|
||||||
this.form = { id: undefined, inNo: undefined, areaId: undefined, productId: undefined, inOperator: undefined, inType: 2, inNum: undefined, remark: undefined }
|
this.form = { id: undefined, inNo: undefined, areaId: undefined, productId: undefined, sourceProjectId: undefined, expenseCategoryId: undefined, inOperator: undefined, inType: 2, inNum: undefined, remark: undefined }
|
||||||
this.resetForm('form')
|
this.resetForm('form')
|
||||||
},
|
},
|
||||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
|
|||||||
@ -32,6 +32,11 @@
|
|||||||
<el-option v-for="dict in dict.type.wms_out_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
<el-option v-for="dict in dict.type.wms_out_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="使用项目" prop="usageProjectId">
|
||||||
|
<el-select v-model="queryParams.usageProjectId" placeholder="请选择使用项目" clearable filterable>
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
@ -62,9 +67,12 @@
|
|||||||
<template slot-scope="scope"><dict-tag :options="dict.type.wms_out_type" :value="scope.row.outType" /></template>
|
<template slot-scope="scope"><dict-tag :options="dict.type.wms_out_type" :value="scope.row.outType" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="数量" align="right" prop="outNum" />
|
<el-table-column label="数量" align="right" prop="outNum" />
|
||||||
|
<el-table-column label="使用项目" prop="usageProjectName" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="来源项目" prop="sourceProjectNames" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="跨项目" width="90" align="center"><template slot-scope="scope"><el-tag v-if="scope.row.crossProjectFlag === 1" type="warning" size="mini">是</el-tag><span v-else>否</span></template></el-table-column>
|
||||||
<el-table-column label="关联单据号" align="center" prop="sourceNo" />
|
<el-table-column label="关联单据号" align="center" prop="sourceNo" />
|
||||||
<el-table-column label="系统操作人" align="center" prop="operator" />
|
<el-table-column label="系统操作人" align="center" prop="operator" />
|
||||||
<el-table-column label="出库人" align="center" prop="outOperator" />
|
<el-table-column label="出库人" align="center" prop="outOperatorName" />
|
||||||
<el-table-column label="出库时间" align="center" prop="outTime" width="180">
|
<el-table-column label="出库时间" align="center" prop="outTime" width="180">
|
||||||
<template slot-scope="scope">{{ parseTime(scope.row.outTime) }}</template>
|
<template slot-scope="scope">{{ parseTime(scope.row.outTime) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -85,6 +93,11 @@
|
|||||||
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
<el-option v-for="item in productOptions" :key="item.id" :label="productLabel(item)" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="使用项目" prop="usageProjectId">
|
||||||
|
<el-select v-model="form.usageProjectId" placeholder="请选择物资使用项目" filterable style="width:100%">
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="出库人" prop="outOperator">
|
<el-form-item label="出库人" prop="outOperator">
|
||||||
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
||||||
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="userValue(item)" />
|
||||||
@ -97,6 +110,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="出库数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" controls-position="right" /></el-form-item>
|
<el-form-item label="出库数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" controls-position="right" /></el-form-item>
|
||||||
<el-form-item label="关联单据号" prop="sourceNo"><el-input v-model="form.sourceNo" placeholder="请输入关联单据号" /></el-form-item>
|
<el-form-item label="关联单据号" prop="sourceNo"><el-input v-model="form.sourceNo" placeholder="请输入关联单据号" /></el-form-item>
|
||||||
|
<el-form-item label="跨项目原因" prop="crossProjectReason"><el-input v-model="form.crossProjectReason" type="textarea" placeholder="若消耗的库存来源项目与使用项目不同,此项必填" /></el-form-item>
|
||||||
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入备注" /></el-form-item>
|
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入备注" /></el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
@ -112,6 +126,7 @@ import { listOutRecord, addOutRecord } from '@/api/warehouse/outRecord'
|
|||||||
import { optionselectArea } from '@/api/warehouse/area'
|
import { optionselectArea } from '@/api/warehouse/area'
|
||||||
import { optionselectProduct } from '@/api/warehouse/product'
|
import { optionselectProduct } from '@/api/warehouse/product'
|
||||||
import { optionselectUser } from '@/api/warehouse/user'
|
import { optionselectUser } from '@/api/warehouse/user'
|
||||||
|
import { projectOptions } from '@/api/project/project'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OutRecord',
|
name: 'OutRecord',
|
||||||
@ -125,12 +140,14 @@ export default {
|
|||||||
areaOptions: [],
|
areaOptions: [],
|
||||||
productOptions: [],
|
productOptions: [],
|
||||||
userOptions: [],
|
userOptions: [],
|
||||||
|
projectOptionList: [],
|
||||||
open: false,
|
open: false,
|
||||||
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined, brand: undefined, category: undefined, spec: undefined, outOperator: undefined, outType: undefined },
|
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined, brand: undefined, category: undefined, spec: undefined, outOperator: undefined, outType: undefined, usageProjectId: undefined },
|
||||||
form: {},
|
form: {},
|
||||||
rules: {
|
rules: {
|
||||||
areaId: [{ required: true, message: '库区不能为空', trigger: 'change' }],
|
areaId: [{ required: true, message: '库区不能为空', trigger: 'change' }],
|
||||||
productId: [{ required: true, message: '产品不能为空', trigger: 'change' }],
|
productId: [{ required: true, message: '产品不能为空', trigger: 'change' }],
|
||||||
|
usageProjectId: [{ required: true, message: '使用项目不能为空', trigger: 'change' }],
|
||||||
outOperator: [{ required: true, message: '出库人不能为空', trigger: 'change' }],
|
outOperator: [{ required: true, message: '出库人不能为空', trigger: 'change' }],
|
||||||
outType: [{ required: true, message: '出库类型不能为空', trigger: 'change' }],
|
outType: [{ required: true, message: '出库类型不能为空', trigger: 'change' }],
|
||||||
outNum: [{ required: true, message: '出库数量不能为空', trigger: 'blur' }]
|
outNum: [{ required: true, message: '出库数量不能为空', trigger: 'blur' }]
|
||||||
@ -154,7 +171,7 @@ export default {
|
|||||||
return user.nickName || user.userName
|
return user.nickName || user.userName
|
||||||
},
|
},
|
||||||
userValue(user) {
|
userValue(user) {
|
||||||
return user.nickName || user.userName
|
return user.userName
|
||||||
},
|
},
|
||||||
areaLabel(area) {
|
areaLabel(area) {
|
||||||
return area.areaName || area.areaCode
|
return area.areaName || area.areaCode
|
||||||
@ -164,6 +181,7 @@ export default {
|
|||||||
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
||||||
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
||||||
},
|
},
|
||||||
|
projectLabel(project) { return project.projectName + '(' + project.projectCode + ')' },
|
||||||
getDistinctOptions(field) {
|
getDistinctOptions(field) {
|
||||||
const values = this.productOptions.map(item => item[field]).filter(item => item !== undefined && item !== null && item !== '')
|
const values = this.productOptions.map(item => item[field]).filter(item => item !== undefined && item !== null && item !== '')
|
||||||
return Array.from(new Set(values))
|
return Array.from(new Set(values))
|
||||||
@ -172,6 +190,7 @@ export default {
|
|||||||
optionselectArea().then(res => { this.areaOptions = res.data })
|
optionselectArea().then(res => { this.areaOptions = res.data })
|
||||||
optionselectProduct().then(res => { this.productOptions = res.data })
|
optionselectProduct().then(res => { this.productOptions = res.data })
|
||||||
optionselectUser().then(res => { this.userOptions = res.data })
|
optionselectUser().then(res => { this.userOptions = res.data })
|
||||||
|
projectOptions('').then(res => { this.projectOptionList = res.data || [] })
|
||||||
},
|
},
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
@ -183,7 +202,7 @@ export default {
|
|||||||
},
|
},
|
||||||
cancel() { this.open = false; this.reset() },
|
cancel() { this.open = false; this.reset() },
|
||||||
reset() {
|
reset() {
|
||||||
this.form = { outNo: undefined, areaId: undefined, productId: undefined, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, remark: undefined }
|
this.form = { outNo: undefined, areaId: undefined, productId: undefined, usageProjectId: undefined, outOperator: undefined, outType: 1, outNum: undefined, sourceNo: undefined, crossProjectReason: undefined, remark: undefined }
|
||||||
this.resetForm('form')
|
this.resetForm('form')
|
||||||
},
|
},
|
||||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
|
|||||||
@ -14,6 +14,16 @@
|
|||||||
<el-option v-for="item in batchFilterOptions" :key="item.id" :label="batchLabel(item)" :value="item.batchNo" />
|
<el-option v-for="item in batchFilterOptions" :key="item.id" :label="batchLabel(item)" :value="item.batchNo" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="采购项目" prop="projectId">
|
||||||
|
<el-select v-model="queryParams.projectId" filterable clearable placeholder="请选择项目">
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId">
|
||||||
|
<el-select v-model="queryParams.expenseCategoryId" filterable clearable placeholder="请选择科目">
|
||||||
|
<el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="申请人" prop="applicantName">
|
<el-form-item label="申请人" prop="applicantName">
|
||||||
<el-select v-model="queryParams.applicantName" filterable clearable placeholder="请选择申请人">
|
<el-select v-model="queryParams.applicantName" filterable clearable placeholder="请选择申请人">
|
||||||
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="item.userName" />
|
<el-option v-for="item in userOptions" :key="item.userId" :label="userLabel(item)" :value="item.userName" />
|
||||||
@ -59,6 +69,10 @@
|
|||||||
<el-table v-loading="loading" :data="purchaseList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="purchaseList" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" :selectable="canOperate" />
|
<el-table-column type="selection" width="55" align="center" :selectable="canOperate" />
|
||||||
<el-table-column label="申请人" prop="applicantName" width="110" />
|
<el-table-column label="申请人" prop="applicantName" width="110" />
|
||||||
|
<el-table-column label="采购项目" min-width="170" show-overflow-tooltip>
|
||||||
|
<template slot-scope="scope">{{ scope.row.projectName }}({{ scope.row.projectCode }})</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="经费科目" prop="expenseCategoryName" width="110" />
|
||||||
<el-table-column label="物料名称" prop="materialName" min-width="150" show-overflow-tooltip />
|
<el-table-column label="物料名称" prop="materialName" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column label="品牌" prop="materialBrand" width="110" show-overflow-tooltip />
|
<el-table-column label="品牌" prop="materialBrand" width="110" show-overflow-tooltip />
|
||||||
<el-table-column label="关联库房物料" min-width="170" show-overflow-tooltip>
|
<el-table-column label="关联库房物料" min-width="170" show-overflow-tooltip>
|
||||||
@ -78,6 +92,9 @@
|
|||||||
<el-table-column label="数量" width="110" align="right">
|
<el-table-column label="数量" width="110" align="right">
|
||||||
<template slot-scope="scope">{{ scope.row.quantity }} {{ scope.row.unit || '' }}</template>
|
<template slot-scope="scope">{{ scope.row.quantity }} {{ scope.row.unit || '' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="预计金额" width="120" align="right">
|
||||||
|
<template slot-scope="scope">{{ money(scope.row.estimatedAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="到货验收" min-width="190">
|
<el-table-column label="到货验收" min-width="190">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<div>已验收 {{ scope.row.acceptedQuantity || 0 }} / {{ scope.row.quantity }}</div>
|
<div>已验收 {{ scope.row.acceptedQuantity || 0 }} / {{ scope.row.quantity }}</div>
|
||||||
@ -95,7 +112,7 @@
|
|||||||
<el-table-column label="操作" align="center" width="285" fixed="right" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" width="285" fixed="right" class-name="small-padding fixed-width">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button v-if="canOperate(scope.row)" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">修改</el-button>
|
<el-button v-if="canOperate(scope.row)" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">修改</el-button>
|
||||||
<el-button size="mini" type="text" icon="el-icon-box" @click="handleArrival(scope.row)" v-hasPermi="['warehouse:purchase:list', 'warehouse:purchase:manage']">到货</el-button>
|
<el-button size="mini" type="text" icon="el-icon-box" @click="handleArrival(scope.row)" v-hasPermi="['warehouse:purchase:list', 'warehouse:purchase:manage']">{{ canOperate(scope.row) ? '到货' : '到货明细' }}</el-button>
|
||||||
<el-button v-if="canOperate(scope.row) && Number(scope.row.acceptedQuantity || 0) === 0" size="mini" type="text" icon="el-icon-refresh" @click="handleStatus(scope.row)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">状态</el-button>
|
<el-button v-if="canOperate(scope.row) && Number(scope.row.acceptedQuantity || 0) === 0" size="mini" type="text" icon="el-icon-refresh" @click="handleStatus(scope.row)" v-hasPermi="['warehouse:purchase:edit', 'warehouse:purchase:manage']">状态</el-button>
|
||||||
<el-button v-if="canManageAll" size="mini" type="text" icon="el-icon-sort" @click="handleAssignBatch(scope.row)" v-hasPermi="['warehouse:purchase:manage']">转批次</el-button>
|
<el-button v-if="canManageAll" size="mini" type="text" icon="el-icon-sort" @click="handleAssignBatch(scope.row)" v-hasPermi="['warehouse:purchase:manage']">转批次</el-button>
|
||||||
<el-button v-if="canOperate(scope.row)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['warehouse:purchase:remove', 'warehouse:purchase:manage']">删除</el-button>
|
<el-button v-if="canOperate(scope.row)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['warehouse:purchase:remove', 'warehouse:purchase:manage']">删除</el-button>
|
||||||
@ -108,6 +125,20 @@
|
|||||||
<el-dialog :title="title" :visible.sync="open" width="720px" custom-class="purchase-dialog" append-to-body>
|
<el-dialog :title="title" :visible.sync="open" width="720px" custom-class="purchase-dialog" append-to-body>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="105px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="105px">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="采购项目" prop="projectId">
|
||||||
|
<el-select v-model="form.projectId" filterable placeholder="请选择采购项目" style="width:100%">
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="经费科目" prop="expenseCategoryId">
|
||||||
|
<el-select v-model="form.expenseCategoryId" filterable placeholder="请选择经费科目" style="width:100%">
|
||||||
|
<el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName + '(' + item.categoryCode + ')'" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="已有产品" prop="productId">
|
<el-form-item label="已有产品" prop="productId">
|
||||||
<el-select v-model="form.productId" filterable clearable placeholder="选择后自动填充;不选择则按下方信息复用或新增" @change="handleExistingProductChange">
|
<el-select v-model="form.productId" filterable clearable placeholder="选择后自动填充;不选择则按下方信息复用或新增" @change="handleExistingProductChange">
|
||||||
@ -151,6 +182,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12"><el-form-item label="需求数量" prop="quantity"><el-input-number v-model="form.quantity" :min="0.001" :precision="3" :controls="false" /></el-form-item></el-col>
|
<el-col :span="12"><el-form-item label="需求数量" prop="quantity"><el-input-number v-model="form.quantity" :min="0.001" :precision="3" :controls="false" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="预计含税单价" prop="estimatedUnitPrice"><el-input-number v-model="form.estimatedUnitPrice" :min="0.01" :precision="2" :controls="false" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12"><el-form-item label="预计总金额"><el-input :value="money(Number(form.quantity || 0) * Number(form.estimatedUnitPrice || 0))" disabled /></el-form-item></el-col>
|
||||||
<el-col :span="12"><el-form-item label="期望到货日期" prop="expectedDate"><el-date-picker v-model="form.expectedDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择日期" /></el-form-item></el-col>
|
<el-col :span="12"><el-form-item label="期望到货日期" prop="expectedDate"><el-date-picker v-model="form.expectedDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择日期" /></el-form-item></el-col>
|
||||||
<el-col :span="24"><el-form-item label="产品备注" prop="productRemark"><el-input v-model="form.productRemark" placeholder="新建产品时写入产品信息,复用已有产品时不覆盖" /></el-form-item></el-col>
|
<el-col :span="24"><el-form-item label="产品备注" prop="productRemark"><el-input v-model="form.productRemark" placeholder="新建产品时写入产品信息,复用已有产品时不覆盖" /></el-form-item></el-col>
|
||||||
<el-col :span="24"><el-form-item label="采购链接" prop="purchaseLink"><el-input v-model="form.purchaseLink" placeholder="请输入商品或供应商链接" /></el-form-item></el-col>
|
<el-col :span="24"><el-form-item label="采购链接" prop="purchaseLink"><el-input v-model="form.purchaseLink" placeholder="请输入商品或供应商链接" /></el-form-item></el-col>
|
||||||
@ -249,42 +282,51 @@
|
|||||||
<el-table-column v-if="gridRows.some(row => row.smartTip)" label="识别提示" min-width="210">
|
<el-table-column v-if="gridRows.some(row => row.smartTip)" label="识别提示" min-width="210">
|
||||||
<template slot-scope="scope"><span class="smart-tip">{{ scope.row.smartTip || '-' }}</span></template>
|
<template slot-scope="scope"><span class="smart-tip">{{ scope.row.smartTip || '-' }}</span></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="采购项目" min-width="190">
|
||||||
|
<template slot-scope="scope"><el-select v-model="scope.row.projectId" filterable @paste.native="handleGridPaste($event, scope.$index, 0)"><el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" /></el-select></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="经费科目" min-width="150">
|
||||||
|
<template slot-scope="scope"><el-select v-model="scope.row.expenseCategoryId" filterable @paste.native="handleGridPaste($event, scope.$index, 1)"><el-option v-for="item in expenseCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" /></el-select></template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="关联物料编码" min-width="150">
|
<el-table-column label="关联物料编码" min-width="150">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-select v-model="scope.row.productCode" filterable clearable allow-create default-first-option @change="handleGridCodeChange(scope.row)" @paste.native="handleGridPaste($event, scope.$index, 0)">
|
<el-select v-model="scope.row.productCode" filterable clearable allow-create default-first-option @change="handleGridCodeChange(scope.row)" @paste.native="handleGridPaste($event, scope.$index, 2)">
|
||||||
<el-option v-for="item in productCodeOptions" :key="item.id" :label="item.productCode + ' / ' + item.productName" :value="item.productCode" />
|
<el-option v-for="item in productCodeOptions" :key="item.id" :label="item.productCode + ' / ' + item.productName" :value="item.productCode" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="物料名称" min-width="170">
|
<el-table-column label="物料名称" min-width="170">
|
||||||
<template slot-scope="scope"><el-select v-model="scope.row.materialName" filterable allow-create default-first-option @change="handleGridNameChange(scope.row)" @paste.native="handleGridPaste($event, scope.$index, 1)"><el-option v-for="item in productNameOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
<template slot-scope="scope"><el-select v-model="scope.row.materialName" filterable allow-create default-first-option @change="handleGridNameChange(scope.row)" @paste.native="handleGridPaste($event, scope.$index, 3)"><el-option v-for="item in productNameOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="品牌" min-width="130">
|
<el-table-column label="品牌" min-width="130">
|
||||||
<template slot-scope="scope"><el-select v-model="scope.row.materialBrand" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 2)"><el-option v-for="item in brandOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
<template slot-scope="scope"><el-select v-model="scope.row.materialBrand" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 4)"><el-option v-for="item in brandOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="分类" min-width="130">
|
<el-table-column label="分类" min-width="130">
|
||||||
<template slot-scope="scope"><el-select v-model="scope.row.category" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 3)"><el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
<template slot-scope="scope"><el-select v-model="scope.row.category" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 5)"><el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="参数规格" min-width="160">
|
<el-table-column label="参数规格" min-width="160">
|
||||||
<template slot-scope="scope"><el-select v-model="scope.row.materialParams" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 4)"><el-option v-for="item in specOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
<template slot-scope="scope"><el-select v-model="scope.row.materialParams" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 6)"><el-option v-for="item in specOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="单位" min-width="100">
|
<el-table-column label="单位" min-width="100">
|
||||||
<template slot-scope="scope"><el-select v-model="scope.row.unit" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 5)"><el-option v-for="item in unitOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
<template slot-scope="scope"><el-select v-model="scope.row.unit" filterable clearable allow-create default-first-option @paste.native="handleGridPaste($event, scope.$index, 7)"><el-option v-for="item in unitOptions" :key="item" :label="item" :value="item" /></el-select></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="需求数量" min-width="110">
|
<el-table-column label="需求数量" min-width="110">
|
||||||
<template slot-scope="scope"><el-input v-model="scope.row.quantity" @paste.native="handleGridPaste($event, scope.$index, 6)" /></template>
|
<template slot-scope="scope"><el-input v-model="scope.row.quantity" @paste.native="handleGridPaste($event, scope.$index, 8)" /></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="预计含税单价" min-width="130">
|
||||||
|
<template slot-scope="scope"><el-input v-model="scope.row.estimatedUnitPrice" @paste.native="handleGridPaste($event, scope.$index, 9)" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="期望到货日期" min-width="140">
|
<el-table-column label="期望到货日期" min-width="140">
|
||||||
<template slot-scope="scope"><el-date-picker v-model="scope.row.expectedDate" type="date" value-format="yyyy-MM-dd" @paste.native="handleGridPaste($event, scope.$index, 7)" /></template>
|
<template slot-scope="scope"><el-date-picker v-model="scope.row.expectedDate" type="date" value-format="yyyy-MM-dd" @paste.native="handleGridPaste($event, scope.$index, 10)" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="采购链接" min-width="210">
|
<el-table-column label="采购链接" min-width="210">
|
||||||
<template slot-scope="scope"><el-input v-model="scope.row.purchaseLink" @paste.native="handleGridPaste($event, scope.$index, 8)" /></template>
|
<template slot-scope="scope"><el-input v-model="scope.row.purchaseLink" @paste.native="handleGridPaste($event, scope.$index, 11)" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="产品备注" min-width="180">
|
<el-table-column label="产品备注" min-width="180">
|
||||||
<template slot-scope="scope"><el-input v-model="scope.row.productRemark" @paste.native="handleGridPaste($event, scope.$index, 9)" /></template>
|
<template slot-scope="scope"><el-input v-model="scope.row.productRemark" @paste.native="handleGridPaste($event, scope.$index, 12)" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="申请备注" min-width="180">
|
<el-table-column label="申请备注" min-width="180">
|
||||||
<template slot-scope="scope"><el-input v-model="scope.row.remark" @paste.native="handleGridPaste($event, scope.$index, 10)" /></template>
|
<template slot-scope="scope"><el-input v-model="scope.row.remark" @paste.native="handleGridPaste($event, scope.$index, 13)" /></template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="55" fixed="right" align="center">
|
<el-table-column label="操作" width="55" fixed="right" align="center">
|
||||||
<template slot-scope="scope"><el-button type="text" icon="el-icon-delete" title="删除行" @click="removeGridRow(scope.$index)" /></template>
|
<template slot-scope="scope"><el-button type="text" icon="el-icon-delete" title="删除行" @click="removeGridRow(scope.$index)" /></template>
|
||||||
@ -311,6 +353,7 @@
|
|||||||
<el-dialog title="分批到货与验收" :visible.sync="arrivalOpen" width="1100px" custom-class="arrival-dialog" append-to-body>
|
<el-dialog title="分批到货与验收" :visible.sync="arrivalOpen" width="1100px" custom-class="arrival-dialog" append-to-body>
|
||||||
<div v-if="arrivalRequest" class="arrival-header">
|
<div v-if="arrivalRequest" class="arrival-header">
|
||||||
<strong>{{ arrivalRequest.materialName }}</strong>
|
<strong>{{ arrivalRequest.materialName }}</strong>
|
||||||
|
<span>{{ arrivalRequest.projectName }} / {{ arrivalRequest.expenseCategoryName }}</span>
|
||||||
<span>采购 {{ arrivalRequest.quantity }} {{ arrivalRequest.unit || '' }}</span>
|
<span>采购 {{ arrivalRequest.quantity }} {{ arrivalRequest.unit || '' }}</span>
|
||||||
<span>已验收 {{ arrivalRequest.acceptedQuantity || 0 }}</span>
|
<span>已验收 {{ arrivalRequest.acceptedQuantity || 0 }}</span>
|
||||||
<span>待验收 {{ arrivalRequest.pendingArrivalQuantity || 0 }}</span>
|
<span>待验收 {{ arrivalRequest.pendingArrivalQuantity || 0 }}</span>
|
||||||
@ -318,6 +361,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-form v-if="arrivalRequest && canOperate(arrivalRequest)" ref="arrivalForm" :model="arrivalForm" :rules="arrivalRules" :inline="true" size="small" class="arrival-form">
|
<el-form v-if="arrivalRequest && canOperate(arrivalRequest)" ref="arrivalForm" :model="arrivalForm" :rules="arrivalRules" :inline="true" size="small" class="arrival-form">
|
||||||
<el-form-item label="本次到货" prop="arrivalQuantity"><el-input-number v-model="arrivalForm.arrivalQuantity" :min="0.001" :max="Number(arrivalRequest.remainingQuantity || 0)" :precision="3" :controls="false" /></el-form-item>
|
<el-form-item label="本次到货" prop="arrivalQuantity"><el-input-number v-model="arrivalForm.arrivalQuantity" :min="0.001" :max="Number(arrivalRequest.remainingQuantity || 0)" :precision="3" :controls="false" /></el-form-item>
|
||||||
|
<el-form-item label="实际含税单价" prop="actualUnitPrice"><el-input-number v-model="arrivalForm.actualUnitPrice" :min="0.01" :precision="2" :controls="false" /></el-form-item>
|
||||||
<el-form-item label="到货日期" prop="arrivalDate"><el-date-picker v-model="arrivalForm.arrivalDate" type="date" value-format="yyyy-MM-dd" /></el-form-item>
|
<el-form-item label="到货日期" prop="arrivalDate"><el-date-picker v-model="arrivalForm.arrivalDate" type="date" value-format="yyyy-MM-dd" /></el-form-item>
|
||||||
<el-form-item label="备注" prop="remark"><el-input v-model="arrivalForm.remark" /></el-form-item>
|
<el-form-item label="备注" prop="remark"><el-input v-model="arrivalForm.remark" /></el-form-item>
|
||||||
<el-form-item><el-button type="primary" :disabled="Number(arrivalRequest.remainingQuantity) <= 0" @click="submitArrival">登记到货</el-button></el-form-item>
|
<el-form-item><el-button type="primary" :disabled="Number(arrivalRequest.remainingQuantity) <= 0" @click="submitArrival">登记到货</el-button></el-form-item>
|
||||||
@ -326,6 +370,8 @@
|
|||||||
<el-table-column label="到货单号" prop="arrivalNo" min-width="175" />
|
<el-table-column label="到货单号" prop="arrivalNo" min-width="175" />
|
||||||
<el-table-column label="到货日期" prop="arrivalDate" width="110" />
|
<el-table-column label="到货日期" prop="arrivalDate" width="110" />
|
||||||
<el-table-column label="数量" prop="arrivalQuantity" width="100" align="right" />
|
<el-table-column label="数量" prop="arrivalQuantity" width="100" align="right" />
|
||||||
|
<el-table-column label="实际单价" prop="actualUnitPrice" width="110" align="right" />
|
||||||
|
<el-table-column label="实际金额" prop="actualAmount" width="120" align="right" />
|
||||||
<el-table-column label="验收状态" width="100" align="center"><template slot-scope="scope"><el-tag :type="acceptanceType(scope.row.acceptanceStatus)" size="mini">{{ acceptanceLabel(scope.row.acceptanceStatus) }}</el-tag></template></el-table-column>
|
<el-table-column label="验收状态" width="100" align="center"><template slot-scope="scope"><el-tag :type="acceptanceType(scope.row.acceptanceStatus)" size="mini">{{ acceptanceLabel(scope.row.acceptanceStatus) }}</el-tag></template></el-table-column>
|
||||||
<el-table-column label="到货备注" prop="remark" min-width="130" show-overflow-tooltip />
|
<el-table-column label="到货备注" prop="remark" min-width="130" show-overflow-tooltip />
|
||||||
<el-table-column label="验收说明" prop="acceptanceRemark" min-width="130" show-overflow-tooltip />
|
<el-table-column label="验收说明" prop="acceptanceRemark" min-width="130" show-overflow-tooltip />
|
||||||
@ -352,6 +398,7 @@
|
|||||||
import { listPurchase, getPurchase, addPurchase, tableAddPurchase, smartImportPurchase, updatePurchase, updatePurchaseStatus, delPurchase, listPurchaseArrival, addPurchaseArrival, acceptPurchaseArrival, revokePurchaseArrival, delPurchaseArrival, listPurchaseBatch, optionPurchaseBatch, filterOptionPurchaseBatch, addPurchaseBatch, updatePurchaseBatch, assignPurchaseBatch, delPurchaseBatch } from '@/api/warehouse/purchase'
|
import { listPurchase, getPurchase, addPurchase, tableAddPurchase, smartImportPurchase, updatePurchase, updatePurchaseStatus, delPurchase, listPurchaseArrival, addPurchaseArrival, acceptPurchaseArrival, revokePurchaseArrival, delPurchaseArrival, listPurchaseBatch, optionPurchaseBatch, filterOptionPurchaseBatch, addPurchaseBatch, updatePurchaseBatch, assignPurchaseBatch, delPurchaseBatch } from '@/api/warehouse/purchase'
|
||||||
import { optionselectProduct } from '@/api/warehouse/product'
|
import { optionselectProduct } from '@/api/warehouse/product'
|
||||||
import { optionselectUser } from '@/api/warehouse/user'
|
import { optionselectUser } from '@/api/warehouse/user'
|
||||||
|
import { projectOptions, categoryOptions } from '@/api/project/project'
|
||||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -367,6 +414,8 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
purchaseList: [],
|
purchaseList: [],
|
||||||
productOptions: [],
|
productOptions: [],
|
||||||
|
projectOptionList: [],
|
||||||
|
expenseCategoryOptions: [],
|
||||||
open: false,
|
open: false,
|
||||||
assignOpen: false,
|
assignOpen: false,
|
||||||
batchOpen: false,
|
batchOpen: false,
|
||||||
@ -379,13 +428,14 @@ export default {
|
|||||||
smartFile: null,
|
smartFile: null,
|
||||||
smartFileList: [],
|
smartFileList: [],
|
||||||
smartImportResultRows: null,
|
smartImportResultRows: null,
|
||||||
gridFields: ['productCode', 'materialName', 'materialBrand', 'category', 'materialParams', 'unit', 'quantity', 'expectedDate', 'purchaseLink', 'productRemark', 'remark'],
|
gridFields: ['projectCode', 'expenseCategoryCode', 'productCode', 'materialName', 'materialBrand', 'category', 'materialParams', 'unit', 'quantity', 'estimatedUnitPrice', 'expectedDate', 'purchaseLink', 'productRemark', 'remark'],
|
||||||
gridHeaderAliases: {
|
gridHeaderAliases: {
|
||||||
|
'项目编码': 'projectCode', '经费科目编码': 'expenseCategoryCode',
|
||||||
'关联物料编码': 'productCode', '物料编码': 'productCode',
|
'关联物料编码': 'productCode', '物料编码': 'productCode',
|
||||||
'物料名称': 'materialName', '产品名称': 'materialName',
|
'物料名称': 'materialName', '产品名称': 'materialName',
|
||||||
'品牌': 'materialBrand', '类别': 'category', '分类': 'category',
|
'品牌': 'materialBrand', '类别': 'category', '分类': 'category',
|
||||||
'参数规格': 'materialParams', '规格型号': 'materialParams',
|
'参数规格': 'materialParams', '规格型号': 'materialParams',
|
||||||
'单位': 'unit', '需求数量': 'quantity', '数量': 'quantity',
|
'单位': 'unit', '需求数量': 'quantity', '数量': 'quantity', '预计含税单价': 'estimatedUnitPrice', '预计单价': 'estimatedUnitPrice',
|
||||||
'期望到货日期': 'expectedDate', '期望到货': 'expectedDate',
|
'期望到货日期': 'expectedDate', '期望到货': 'expectedDate',
|
||||||
'采购链接': 'purchaseLink', '产品备注': 'productRemark', '申请备注': 'remark'
|
'采购链接': 'purchaseLink', '产品备注': 'productRemark', '申请备注': 'remark'
|
||||||
},
|
},
|
||||||
@ -422,13 +472,16 @@ export default {
|
|||||||
{ value: 4, label: '已归档', type: '' },
|
{ value: 4, label: '已归档', type: '' },
|
||||||
{ value: 5, label: '已取消', type: 'danger' }
|
{ value: 5, label: '已取消', type: 'danger' }
|
||||||
],
|
],
|
||||||
queryParams: { pageNum: 1, pageSize: 10, materialName: undefined, purchaseStatus: undefined, purchaseBatchNo: undefined, applicantName: undefined },
|
queryParams: { pageNum: 1, pageSize: 10, materialName: undefined, purchaseStatus: undefined, purchaseBatchNo: undefined, applicantName: undefined, projectId: undefined, expenseCategoryId: undefined },
|
||||||
form: {},
|
form: {},
|
||||||
assignForm: {},
|
assignForm: {},
|
||||||
batchForm: {},
|
batchForm: {},
|
||||||
rules: {
|
rules: {
|
||||||
|
projectId: [{ required: true, message: '采购项目不能为空', trigger: 'change' }],
|
||||||
|
expenseCategoryId: [{ required: true, message: '经费科目不能为空', trigger: 'change' }],
|
||||||
materialName: [{ required: true, message: '物料名称不能为空', trigger: 'blur' }],
|
materialName: [{ required: true, message: '物料名称不能为空', trigger: 'blur' }],
|
||||||
quantity: [{ required: true, message: '需求数量不能为空', trigger: 'change' }],
|
quantity: [{ required: true, message: '需求数量不能为空', trigger: 'change' }],
|
||||||
|
estimatedUnitPrice: [{ required: true, message: '预计含税单价不能为空', trigger: 'change' }],
|
||||||
purchaseLink: [{ pattern: /^https?:\/\//i, message: '链接必须以 http:// 或 https:// 开头', trigger: 'blur' }]
|
purchaseLink: [{ pattern: /^https?:\/\//i, message: '链接必须以 http:// 或 https:// 开头', trigger: 'blur' }]
|
||||||
},
|
},
|
||||||
assignRules: {
|
assignRules: {
|
||||||
@ -441,6 +494,7 @@ export default {
|
|||||||
statusRules: { purchaseStatus: [{ required: true, message: '请选择采购状态', trigger: 'change' }] },
|
statusRules: { purchaseStatus: [{ required: true, message: '请选择采购状态', trigger: 'change' }] },
|
||||||
arrivalRules: {
|
arrivalRules: {
|
||||||
arrivalQuantity: [{ required: true, message: '到货数量不能为空', trigger: 'change' }],
|
arrivalQuantity: [{ required: true, message: '到货数量不能为空', trigger: 'change' }],
|
||||||
|
actualUnitPrice: [{ required: true, message: '实际含税单价不能为空', trigger: 'change' }],
|
||||||
arrivalDate: [{ required: true, message: '到货日期不能为空', trigger: 'change' }]
|
arrivalDate: [{ required: true, message: '到货日期不能为空', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,10 +516,17 @@ export default {
|
|||||||
this.getList()
|
this.getList()
|
||||||
this.getProductOptions()
|
this.getProductOptions()
|
||||||
this.getUserOptions()
|
this.getUserOptions()
|
||||||
|
this.getFundingOptions()
|
||||||
this.searchBatchFilterOptions('')
|
this.searchBatchFilterOptions('')
|
||||||
if (this.canManageAll) this.getBatchOptions()
|
if (this.canManageAll) this.getBatchOptions()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
money(value) { return Number(value || 0).toFixed(2) },
|
||||||
|
projectLabel(project) { return project.projectName + '(' + project.projectCode + ')' },
|
||||||
|
getFundingOptions() {
|
||||||
|
projectOptions('').then(response => { this.projectOptionList = response.data || [] })
|
||||||
|
categoryOptions().then(response => { this.expenseCategoryOptions = response.data || [] })
|
||||||
|
},
|
||||||
userLabel(user) {
|
userLabel(user) {
|
||||||
const account = user.userName || ''
|
const account = user.userName || ''
|
||||||
return user.nickName && user.nickName !== account ? `${user.nickName}(${account})` : account
|
return user.nickName && user.nickName !== account ? `${user.nickName}(${account})` : account
|
||||||
@ -487,7 +548,7 @@ export default {
|
|||||||
}).finally(() => { this.loading = false })
|
}).finally(() => { this.loading = false })
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.form = { id: undefined, productId: undefined, materialName: undefined, materialBrand: undefined, category: undefined, materialParams: undefined, productRemark: undefined, purchaseLink: undefined, quantity: 1, unit: undefined, expectedDate: undefined, remark: undefined }
|
this.form = { id: undefined, projectId: undefined, expenseCategoryId: undefined, productId: undefined, materialName: undefined, materialBrand: undefined, category: undefined, materialParams: undefined, productRemark: undefined, purchaseLink: undefined, quantity: 1, estimatedUnitPrice: undefined, unit: undefined, expectedDate: undefined, remark: undefined }
|
||||||
this.resetForm('form')
|
this.resetForm('form')
|
||||||
},
|
},
|
||||||
cancel() { this.open = false; this.reset() },
|
cancel() { this.open = false; this.reset() },
|
||||||
@ -557,7 +618,7 @@ export default {
|
|||||||
}).finally(() => { this.arrivalLoading = false })
|
}).finally(() => { this.arrivalLoading = false })
|
||||||
},
|
},
|
||||||
resetArrivalForm() {
|
resetArrivalForm() {
|
||||||
this.arrivalForm = { purchaseRequestId: this.arrivalRequest && this.arrivalRequest.id, arrivalQuantity: undefined, arrivalDate: this.parseTime(new Date(), '{y}-{m}-{d}'), remark: undefined }
|
this.arrivalForm = { purchaseRequestId: this.arrivalRequest && this.arrivalRequest.id, arrivalQuantity: undefined, actualUnitPrice: this.arrivalRequest && this.arrivalRequest.estimatedUnitPrice, arrivalDate: this.parseTime(new Date(), '{y}-{m}-{d}'), remark: undefined }
|
||||||
this.$nextTick(() => this.$refs.arrivalForm && this.$refs.arrivalForm.clearValidate())
|
this.$nextTick(() => this.$refs.arrivalForm && this.$refs.arrivalForm.clearValidate())
|
||||||
},
|
},
|
||||||
submitArrival() {
|
submitArrival() {
|
||||||
@ -665,7 +726,7 @@ export default {
|
|||||||
}).finally(() => { this.smartImportLoading = false })
|
}).finally(() => { this.smartImportLoading = false })
|
||||||
},
|
},
|
||||||
emptyGridRow() {
|
emptyGridRow() {
|
||||||
return { productId: undefined, productCode: undefined, materialName: undefined, materialBrand: undefined, category: undefined, materialParams: undefined, unit: undefined, quantity: 1, expectedDate: undefined, purchaseLink: undefined, productRemark: undefined, remark: undefined }
|
return { projectId: undefined, projectCode: undefined, expenseCategoryId: undefined, expenseCategoryCode: undefined, productId: undefined, productCode: undefined, materialName: undefined, materialBrand: undefined, category: undefined, materialParams: undefined, unit: undefined, quantity: 1, estimatedUnitPrice: undefined, expectedDate: undefined, purchaseLink: undefined, productRemark: undefined, remark: undefined }
|
||||||
},
|
},
|
||||||
addGridRow() { this.gridRows.push(this.emptyGridRow()) },
|
addGridRow() { this.gridRows.push(this.emptyGridRow()) },
|
||||||
removeGridRow(index) {
|
removeGridRow(index) {
|
||||||
@ -698,7 +759,7 @@ export default {
|
|||||||
cells.forEach((value, columnOffset) => {
|
cells.forEach((value, columnOffset) => {
|
||||||
const field = hasHeader ? headerFields[columnOffset] : this.gridFields[startColumn + columnOffset]
|
const field = hasHeader ? headerFields[columnOffset] : this.gridFields[startColumn + columnOffset]
|
||||||
if (!field) return
|
if (!field) return
|
||||||
this.$set(target, field, field === 'quantity' ? this.parseGridNumber(value) : (field === 'expectedDate' ? this.normalizeGridDate(value) : value.trim()))
|
this.$set(target, field, ['quantity', 'estimatedUnitPrice'].includes(field) ? this.parseGridNumber(value) : (field === 'expectedDate' ? this.normalizeGridDate(value) : value.trim()))
|
||||||
})
|
})
|
||||||
if (target.productCode) this.handleGridCodeChange(target)
|
if (target.productCode) this.handleGridCodeChange(target)
|
||||||
else if (target.materialName) this.handleGridNameChange(target)
|
else if (target.materialName) this.handleGridNameChange(target)
|
||||||
@ -743,14 +804,26 @@ export default {
|
|||||||
if (!rows.length) return this.$modal.msgError('请至少录入一行采购数据')
|
if (!rows.length) return this.$modal.msgError('请至少录入一行采购数据')
|
||||||
for (let index = 0; index < rows.length; index++) {
|
for (let index = 0; index < rows.length; index++) {
|
||||||
const row = rows[index]
|
const row = rows[index]
|
||||||
|
if (!row.projectId && row.projectCode) {
|
||||||
|
const project = this.projectOptionList.find(item => this.sameText(item.projectCode, row.projectCode))
|
||||||
|
if (project) row.projectId = project.id
|
||||||
|
}
|
||||||
|
if (!row.expenseCategoryId && row.expenseCategoryCode) {
|
||||||
|
const category = this.expenseCategoryOptions.find(item => this.sameText(item.categoryCode, row.expenseCategoryCode))
|
||||||
|
if (category) row.expenseCategoryId = category.id
|
||||||
|
}
|
||||||
|
if (!row.projectId) return this.$modal.msgError('第' + (index + 1) + '行采购项目不能为空')
|
||||||
|
if (!row.expenseCategoryId) return this.$modal.msgError('第' + (index + 1) + '行经费科目不能为空')
|
||||||
if (!row.materialName) return this.$modal.msgError('第' + (index + 1) + '行物料名称不能为空')
|
if (!row.materialName) return this.$modal.msgError('第' + (index + 1) + '行物料名称不能为空')
|
||||||
if (!Number.isFinite(Number(row.quantity)) || Number(row.quantity) <= 0) return this.$modal.msgError('第' + (index + 1) + '行数量必须大于0')
|
if (!Number.isFinite(Number(row.quantity)) || Number(row.quantity) <= 0) return this.$modal.msgError('第' + (index + 1) + '行数量必须大于0')
|
||||||
|
if (!Number.isFinite(Number(row.estimatedUnitPrice)) || Number(row.estimatedUnitPrice) <= 0) return this.$modal.msgError('第' + (index + 1) + '行预计单价必须大于0')
|
||||||
if (row.purchaseLink && !/^https?:\/\//i.test(row.purchaseLink)) return this.$modal.msgError('第' + (index + 1) + '行采购链接格式不正确')
|
if (row.purchaseLink && !/^https?:\/\//i.test(row.purchaseLink)) return this.$modal.msgError('第' + (index + 1) + '行采购链接格式不正确')
|
||||||
row.quantity = Number(row.quantity)
|
row.quantity = Number(row.quantity)
|
||||||
|
row.estimatedUnitPrice = Number(row.estimatedUnitPrice)
|
||||||
}
|
}
|
||||||
this.gridSaving = true
|
this.gridSaving = true
|
||||||
const payload = rows.map(row => {
|
const payload = rows.map(row => {
|
||||||
const item = { productId: row.productId }
|
const item = { productId: row.productId, projectId: row.projectId, expenseCategoryId: row.expenseCategoryId }
|
||||||
this.gridFields.forEach(field => { item[field] = row[field] })
|
this.gridFields.forEach(field => { item[field] = row[field] })
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
|
|||||||
@ -37,8 +37,9 @@
|
|||||||
<el-table-column label="最近盘库时间" align="center" prop="lastCheckTime" width="180">
|
<el-table-column label="最近盘库时间" align="center" prop="lastCheckTime" width="180">
|
||||||
<template slot-scope="scope">{{ parseTime(scope.row.lastCheckTime) }}</template>
|
<template slot-scope="scope">{{ parseTime(scope.row.lastCheckTime) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
<el-table-column label="操作" align="center" width="170" fixed="right">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-collection" @click="handleLots(scope.row)" v-hasPermi="['warehouse:stock:list']">批次</el-button>
|
||||||
<el-button size="mini" type="text" icon="el-icon-sold-out" @click="handleOut(scope.row)" v-hasPermi="['warehouse:stock:out']">出库</el-button>
|
<el-button size="mini" type="text" icon="el-icon-sold-out" @click="handleOut(scope.row)" v-hasPermi="['warehouse:stock:out']">出库</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -51,6 +52,11 @@
|
|||||||
<el-form-item label="库区"><el-input :value="form.areaName" disabled /></el-form-item>
|
<el-form-item label="库区"><el-input :value="form.areaName" disabled /></el-form-item>
|
||||||
<el-form-item label="产品"><el-input :value="productLabel(form)" disabled /></el-form-item>
|
<el-form-item label="产品"><el-input :value="productLabel(form)" disabled /></el-form-item>
|
||||||
<el-form-item label="当前库存"><el-input :value="form.stockNum" disabled /></el-form-item>
|
<el-form-item label="当前库存"><el-input :value="form.stockNum" disabled /></el-form-item>
|
||||||
|
<el-form-item label="使用项目" prop="usageProjectId">
|
||||||
|
<el-select v-model="form.usageProjectId" placeholder="请选择物资使用项目" filterable style="width:100%">
|
||||||
|
<el-option v-for="item in projectOptionList" :key="item.id" :label="projectLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="出库单号" prop="outNo"><el-input v-model="form.outNo" placeholder="不填则自动生成" /></el-form-item>
|
<el-form-item label="出库单号" prop="outNo"><el-input v-model="form.outNo" placeholder="不填则自动生成" /></el-form-item>
|
||||||
<el-form-item label="出库人" prop="outOperator">
|
<el-form-item label="出库人" prop="outOperator">
|
||||||
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
<el-select v-model="form.outOperator" placeholder="请选择出库人" filterable>
|
||||||
@ -64,6 +70,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="出库数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" controls-position="right" /></el-form-item>
|
<el-form-item label="出库数量" prop="outNum"><el-input-number v-model="form.outNum" :min="0.001" :precision="3" :step="1" controls-position="right" /></el-form-item>
|
||||||
<el-form-item label="关联单据号" prop="sourceNo"><el-input v-model="form.sourceNo" placeholder="请输入关联单据号" /></el-form-item>
|
<el-form-item label="关联单据号" prop="sourceNo"><el-input v-model="form.sourceNo" placeholder="请输入关联单据号" /></el-form-item>
|
||||||
|
<el-form-item label="跨项目原因" prop="crossProjectReason"><el-input v-model="form.crossProjectReason" type="textarea" placeholder="若库存来源项目与使用项目不同,此项必填" /></el-form-item>
|
||||||
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入备注" /></el-form-item>
|
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入备注" /></el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
@ -71,13 +78,27 @@
|
|||||||
<el-button @click="cancel">取 消</el-button>
|
<el-button @click="cancel">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="库存来源批次" :visible.sync="lotOpen" width="980px" append-to-body>
|
||||||
|
<el-table v-loading="lotLoading" :data="lotList" border>
|
||||||
|
<el-table-column label="批次号" prop="lotNo" min-width="175" />
|
||||||
|
<el-table-column label="来源项目" prop="sourceProjectName" min-width="160" />
|
||||||
|
<el-table-column label="经费科目" prop="expenseCategoryName" min-width="110" />
|
||||||
|
<el-table-column label="采购单号" prop="purchaseRequestNo" min-width="160" />
|
||||||
|
<el-table-column label="入库数量" prop="receivedQuantity" align="right" />
|
||||||
|
<el-table-column label="可用数量" prop="availableQuantity" align="right" />
|
||||||
|
<el-table-column label="单位成本" prop="unitCost" align="right" />
|
||||||
|
<el-table-column label="入库时间" prop="createTime" width="165" />
|
||||||
|
</el-table>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listStock, outStock } from '@/api/warehouse/stock'
|
import { listStock, outStock, listStockLots } from '@/api/warehouse/stock'
|
||||||
import { optionselectArea } from '@/api/warehouse/area'
|
import { optionselectArea } from '@/api/warehouse/area'
|
||||||
import { optionselectUser } from '@/api/warehouse/user'
|
import { optionselectUser } from '@/api/warehouse/user'
|
||||||
|
import { projectOptions } from '@/api/project/project'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StockInfo',
|
name: 'StockInfo',
|
||||||
@ -90,10 +111,15 @@ export default {
|
|||||||
stockList: [],
|
stockList: [],
|
||||||
areaOptions: [],
|
areaOptions: [],
|
||||||
userOptions: [],
|
userOptions: [],
|
||||||
|
projectOptionList: [],
|
||||||
|
lotOpen: false,
|
||||||
|
lotLoading: false,
|
||||||
|
lotList: [],
|
||||||
open: false,
|
open: false,
|
||||||
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined },
|
queryParams: { pageNum: 1, pageSize: 10, areaId: undefined, productName: undefined },
|
||||||
form: {},
|
form: {},
|
||||||
rules: {
|
rules: {
|
||||||
|
usageProjectId: [{ required: true, message: '使用项目不能为空', trigger: 'change' }],
|
||||||
outOperator: [{ required: true, message: '出库人不能为空', trigger: 'change' }],
|
outOperator: [{ required: true, message: '出库人不能为空', trigger: 'change' }],
|
||||||
outType: [{ required: true, message: '出库类型不能为空', trigger: 'change' }],
|
outType: [{ required: true, message: '出库类型不能为空', trigger: 'change' }],
|
||||||
outNum: [{ required: true, message: '出库数量不能为空', trigger: 'blur' }]
|
outNum: [{ required: true, message: '出库数量不能为空', trigger: 'blur' }]
|
||||||
@ -109,7 +135,7 @@ export default {
|
|||||||
return user.nickName || user.userName
|
return user.nickName || user.userName
|
||||||
},
|
},
|
||||||
userValue(user) {
|
userValue(user) {
|
||||||
return user.nickName || user.userName
|
return user.userName
|
||||||
},
|
},
|
||||||
areaLabel(area) {
|
areaLabel(area) {
|
||||||
return area.areaName || area.areaCode
|
return area.areaName || area.areaCode
|
||||||
@ -119,9 +145,11 @@ export default {
|
|||||||
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
const details = [product.brand, product.category, product.spec, product.unit].filter(item => item)
|
||||||
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
return details.length ? name + '(' + details.join(' / ') + ')' : name
|
||||||
},
|
},
|
||||||
|
projectLabel(project) { return project.projectName + '(' + project.projectCode + ')' },
|
||||||
getOptions() {
|
getOptions() {
|
||||||
optionselectArea().then(res => { this.areaOptions = res.data })
|
optionselectArea().then(res => { this.areaOptions = res.data })
|
||||||
optionselectUser().then(res => { this.userOptions = res.data })
|
optionselectUser().then(res => { this.userOptions = res.data })
|
||||||
|
projectOptions('').then(res => { this.projectOptionList = res.data || [] })
|
||||||
},
|
},
|
||||||
getList() {
|
getList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
@ -141,11 +169,13 @@ export default {
|
|||||||
spec: row.spec,
|
spec: row.spec,
|
||||||
unit: row.unit,
|
unit: row.unit,
|
||||||
stockNum: row.stockNum,
|
stockNum: row.stockNum,
|
||||||
|
usageProjectId: undefined,
|
||||||
outNo: undefined,
|
outNo: undefined,
|
||||||
outOperator: undefined,
|
outOperator: undefined,
|
||||||
outType: 1,
|
outType: 1,
|
||||||
outNum: undefined,
|
outNum: undefined,
|
||||||
sourceNo: undefined,
|
sourceNo: undefined,
|
||||||
|
crossProjectReason: undefined,
|
||||||
remark: undefined
|
remark: undefined
|
||||||
}
|
}
|
||||||
this.resetForm('form')
|
this.resetForm('form')
|
||||||
@ -154,6 +184,11 @@ export default {
|
|||||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
|
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
|
||||||
handleOut(row) { this.resetFormData(row); this.open = true },
|
handleOut(row) { this.resetFormData(row); this.open = true },
|
||||||
|
handleLots(row) {
|
||||||
|
this.lotOpen = true
|
||||||
|
this.lotLoading = true
|
||||||
|
listStockLots({ areaId: row.areaId, productId: row.productId }).then(res => { this.lotList = res.data || [] }).finally(() => { this.lotLoading = false })
|
||||||
|
},
|
||||||
submitOut() {
|
submitOut() {
|
||||||
this.$refs.form.validate(valid => {
|
this.$refs.form.validate(valid => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
|
|||||||
@ -16,6 +16,7 @@ create table if not exists wms_batch_job (
|
|||||||
create_time datetime default null comment '创建时间',
|
create_time datetime default null comment '创建时间',
|
||||||
update_time datetime default null comment '更新时间',
|
update_time datetime default null comment '更新时间',
|
||||||
primary key (id),
|
primary key (id),
|
||||||
|
key idx_wms_batch_job_creator (create_by, id),
|
||||||
unique key uk_wms_batch_job_no (job_no)
|
unique key uk_wms_batch_job_no (job_no)
|
||||||
) engine=innodb default charset=utf8mb4 comment='WMS批量导入任务';
|
) engine=innodb default charset=utf8mb4 comment='WMS批量导入任务';
|
||||||
|
|
||||||
@ -45,5 +46,34 @@ create table if not exists wms_batch_item (
|
|||||||
update_time datetime default null comment '更新时间',
|
update_time datetime default null comment '更新时间',
|
||||||
primary key (id),
|
primary key (id),
|
||||||
key idx_wms_batch_item_job (job_id),
|
key idx_wms_batch_item_job (job_id),
|
||||||
|
key idx_wms_batch_item_job_status (job_id, status),
|
||||||
key idx_wms_batch_item_status (status)
|
key idx_wms_batch_item_status (status)
|
||||||
) engine=innodb default charset=utf8mb4 comment='WMS批量导入明细';
|
) engine=innodb default charset=utf8mb4 comment='WMS批量导入明细';
|
||||||
|
|
||||||
|
set @wms_batch_item_job_status_index = (
|
||||||
|
select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'wms_batch_item'
|
||||||
|
and index_name = 'idx_wms_batch_item_job_status'
|
||||||
|
);
|
||||||
|
set @wms_batch_item_job_status_sql = if(
|
||||||
|
@wms_batch_item_job_status_index = 0,
|
||||||
|
'alter table wms_batch_item add key idx_wms_batch_item_job_status (job_id, status)',
|
||||||
|
'select 1'
|
||||||
|
);
|
||||||
|
prepare wms_batch_item_job_status_stmt from @wms_batch_item_job_status_sql;
|
||||||
|
execute wms_batch_item_job_status_stmt;
|
||||||
|
deallocate prepare wms_batch_item_job_status_stmt;
|
||||||
|
|
||||||
|
set @wms_batch_job_creator_index = (
|
||||||
|
select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'wms_batch_job'
|
||||||
|
and index_name = 'idx_wms_batch_job_creator'
|
||||||
|
);
|
||||||
|
set @wms_batch_job_creator_sql = if(
|
||||||
|
@wms_batch_job_creator_index = 0,
|
||||||
|
'alter table wms_batch_job add key idx_wms_batch_job_creator (create_by, id)',
|
||||||
|
'select 1'
|
||||||
|
);
|
||||||
|
prepare wms_batch_job_creator_stmt from @wms_batch_job_creator_sql;
|
||||||
|
execute wms_batch_job_creator_stmt;
|
||||||
|
deallocate prepare wms_batch_job_creator_stmt;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ create table warehouse_area (
|
|||||||
create_time datetime default null comment '创建时间',
|
create_time datetime default null comment '创建时间',
|
||||||
update_time datetime default null comment '更新时间',
|
update_time datetime default null comment '更新时间',
|
||||||
primary key (id),
|
primary key (id),
|
||||||
|
key idx_warehouse_area_name (area_name),
|
||||||
unique key uk_warehouse_area_code (area_code)
|
unique key uk_warehouse_area_code (area_code)
|
||||||
) engine=innodb auto_increment=1 comment='库区定义表';
|
) engine=innodb auto_increment=1 comment='库区定义表';
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ create table product_info (
|
|||||||
create_time datetime default null comment '创建时间',
|
create_time datetime default null comment '创建时间',
|
||||||
update_time datetime default null comment '更新时间',
|
update_time datetime default null comment '更新时间',
|
||||||
primary key (id),
|
primary key (id),
|
||||||
|
key idx_product_info_identity (product_name, brand, category, spec),
|
||||||
unique key uk_product_info_code (product_code)
|
unique key uk_product_info_code (product_code)
|
||||||
) engine=innodb auto_increment=1 comment='产品信息表';
|
) engine=innodb auto_increment=1 comment='产品信息表';
|
||||||
|
|
||||||
@ -124,6 +126,20 @@ insert into sys_dict_data values(108, 3, '销售出库', '3', 'wms_out_type', ''
|
|||||||
insert into sys_dict_data values(109, 1, '待确认', '0', 'wms_check_status', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '');
|
insert into sys_dict_data values(109, 1, '待确认', '0', 'wms_check_status', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '');
|
||||||
insert into sys_dict_data values(110, 2, '盘点生效', '1', 'wms_check_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '');
|
insert into sys_dict_data values(110, 2, '盘点生效', '1', 'wms_check_status', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '');
|
||||||
|
|
||||||
|
-- 仓库记录按账号关联用户昵称,给频繁关联字段补充索引。
|
||||||
|
set @sys_user_name_index = (
|
||||||
|
select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'sys_user' and index_name = 'idx_sys_user_user_name'
|
||||||
|
);
|
||||||
|
set @sys_user_name_index_sql = if(
|
||||||
|
@sys_user_name_index = 0,
|
||||||
|
'alter table sys_user add key idx_sys_user_user_name (user_name)',
|
||||||
|
'select 1'
|
||||||
|
);
|
||||||
|
prepare sys_user_name_index_stmt from @sys_user_name_index_sql;
|
||||||
|
execute sys_user_name_index_stmt;
|
||||||
|
deallocate prepare sys_user_name_index_stmt;
|
||||||
|
|
||||||
insert into sys_menu values('2000', '库存管理', '0', '5', 'warehouse', null, '', '', 1, 0, 'M', '0', '0', '', 'dict', 'admin', sysdate(), '', null, '库存管理目录');
|
insert into sys_menu values('2000', '库存管理', '0', '5', 'warehouse', null, '', '', 1, 0, 'M', '0', '0', '', 'dict', 'admin', sysdate(), '', null, '库存管理目录');
|
||||||
insert into sys_menu values('2001', '库区定义', '2000', '1', 'area', 'warehouse/area/index', '', '', 1, 0, 'C', '0', '0', 'warehouse:area:list', 'tree', 'admin', sysdate(), '', null, '库区定义菜单');
|
insert into sys_menu values('2001', '库区定义', '2000', '1', 'area', 'warehouse/area/index', '', '', 1, 0, 'C', '0', '0', 'warehouse:area:list', 'tree', 'admin', sysdate(), '', null, '库区定义菜单');
|
||||||
insert into sys_menu values('2002', '产品信息', '2000', '2', 'product', 'warehouse/product/index', '', '', 1, 0, 'C', '0', '0', 'warehouse:product:list', 'dict', 'admin', sysdate(), '', null, '产品信息菜单');
|
insert into sys_menu values('2002', '产品信息', '2000', '2', 'product', 'warehouse/product/index', '', '', 1, 0, 'C', '0', '0', 'warehouse:product:list', 'dict', 'admin', sysdate(), '', null, '产品信息菜单');
|
||||||
|
|||||||
64
sql/wms_optimization_20260916.sql
Normal file
64
sql/wms_optimization_20260916.sql
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
-- WMS性能与人员账号规范化升级脚本,可在现有数据库上重复执行。
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'sys_user' and index_name = 'idx_sys_user_user_name');
|
||||||
|
set @ddl = if(@idx_exists = 0, 'alter table sys_user add key idx_sys_user_user_name (user_name)', 'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'warehouse_area' and index_name = 'idx_warehouse_area_name');
|
||||||
|
set @ddl = if(@idx_exists = 0, 'alter table warehouse_area add key idx_warehouse_area_name (area_name)', 'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'product_info' and index_name = 'idx_product_info_identity');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table product_info add key idx_product_info_identity (product_name, brand, category, spec)', 'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'purchase_arrival'
|
||||||
|
and index_name = 'idx_purchase_arrival_request_status');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table purchase_arrival add key idx_purchase_arrival_request_status (purchase_request_id, acceptance_status)',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'wms_batch_item'
|
||||||
|
and index_name = 'idx_wms_batch_item_job_status');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table wms_batch_item add key idx_wms_batch_item_job_status (job_id, status)', 'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'wms_batch_job'
|
||||||
|
and index_name = 'idx_wms_batch_job_creator');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table wms_batch_job add key idx_wms_batch_job_creator (create_by, id)', 'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
-- 仅在昵称唯一对应一个有效账号时迁移历史数据,避免同名用户被错误关联。
|
||||||
|
update in_record ir
|
||||||
|
join (
|
||||||
|
select nick_name, min(user_name) as user_name
|
||||||
|
from sys_user where del_flag = '0' and nick_name is not null and nick_name != ''
|
||||||
|
group by nick_name having count(*) = 1
|
||||||
|
) u on u.nick_name = ir.in_operator
|
||||||
|
set ir.in_operator = u.user_name;
|
||||||
|
|
||||||
|
update out_record orr
|
||||||
|
join (
|
||||||
|
select nick_name, min(user_name) as user_name
|
||||||
|
from sys_user where del_flag = '0' and nick_name is not null and nick_name != ''
|
||||||
|
group by nick_name having count(*) = 1
|
||||||
|
) u on u.nick_name = orr.out_operator
|
||||||
|
set orr.out_operator = u.user_name;
|
||||||
|
|
||||||
|
update stock_check_record scr
|
||||||
|
join (
|
||||||
|
select nick_name, min(user_name) as user_name
|
||||||
|
from sys_user where del_flag = '0' and nick_name is not null and nick_name != ''
|
||||||
|
group by nick_name having count(*) = 1
|
||||||
|
) u on u.nick_name = scr.check_user
|
||||||
|
set scr.check_user = u.user_name;
|
||||||
362
sql/wms_project_management_20260922.sql
Normal file
362
sql/wms_project_management_20260922.sql
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
-- 项目经费、库存来源批次、跨项目使用及 AI 指令审计。
|
||||||
|
-- 用于从当前 WMS 结构升级。所有增量字段和索引均先检查再创建,允许在中断后重新执行。
|
||||||
|
-- 执行前必须显式确认 database() 是目标库,并完成数据库备份。
|
||||||
|
|
||||||
|
create table if not exists project_info (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
project_code varchar(64) not null comment '项目编码',
|
||||||
|
project_name varchar(200) not null comment '项目名称',
|
||||||
|
manager_id bigint(20) default null comment '项目负责人用户ID',
|
||||||
|
dept_id bigint(20) default null comment '归属部门ID',
|
||||||
|
start_date date default null comment '开始日期',
|
||||||
|
end_date date default null comment '结束日期',
|
||||||
|
project_status tinyint(1) not null default 1 comment '状态:0筹备 1进行中 2已完成 3已归档',
|
||||||
|
currency varchar(8) not null default 'CNY' comment '币种',
|
||||||
|
remark varchar(500) default null comment '备注',
|
||||||
|
create_by varchar(64) default null comment '创建人',
|
||||||
|
create_time datetime default null comment '创建时间',
|
||||||
|
update_by varchar(64) default null comment '更新人',
|
||||||
|
update_time datetime default null comment '更新时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_project_info_code (project_code),
|
||||||
|
key idx_project_info_name (project_name),
|
||||||
|
key idx_project_info_manager (manager_id),
|
||||||
|
key idx_project_info_status (project_status)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='项目主表';
|
||||||
|
|
||||||
|
create table if not exists project_member (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
project_id bigint(20) not null comment '项目ID',
|
||||||
|
user_id bigint(20) not null comment '用户ID',
|
||||||
|
member_role varchar(32) not null default 'MEMBER' comment '成员角色',
|
||||||
|
create_by varchar(64) default null comment '创建人',
|
||||||
|
create_time datetime default null comment '创建时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_project_member (project_id, user_id),
|
||||||
|
key idx_project_member_user (user_id)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='项目成员表';
|
||||||
|
|
||||||
|
create table if not exists expense_category (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
category_code varchar(64) not null comment '经费科目编码',
|
||||||
|
category_name varchar(100) not null comment '经费科目名称',
|
||||||
|
sort_order int not null default 0 comment '显示顺序',
|
||||||
|
status tinyint(1) not null default 0 comment '状态:0正常 1停用',
|
||||||
|
remark varchar(500) default null comment '备注',
|
||||||
|
create_by varchar(64) default null comment '创建人',
|
||||||
|
create_time datetime default null comment '创建时间',
|
||||||
|
update_by varchar(64) default null comment '更新人',
|
||||||
|
update_time datetime default null comment '更新时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_expense_category_code (category_code),
|
||||||
|
unique key uk_expense_category_name (category_name)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='项目经费科目';
|
||||||
|
|
||||||
|
create table if not exists project_budget (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
project_id bigint(20) not null comment '项目ID',
|
||||||
|
expense_category_id bigint(20) not null comment '经费科目ID',
|
||||||
|
budget_amount decimal(18,2) not null default 0.00 comment '预算金额',
|
||||||
|
committed_amount decimal(18,2) not null default 0.00 comment '已占用金额',
|
||||||
|
spent_amount decimal(18,2) not null default 0.00 comment '已支出金额',
|
||||||
|
alert_percent decimal(5,2) not null default 80.00 comment '预警百分比',
|
||||||
|
version int not null default 0 comment '乐观锁版本',
|
||||||
|
remark varchar(500) default null comment '备注',
|
||||||
|
create_by varchar(64) default null comment '创建人',
|
||||||
|
create_time datetime default null comment '创建时间',
|
||||||
|
update_by varchar(64) default null comment '更新人',
|
||||||
|
update_time datetime default null comment '更新时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_project_budget (project_id, expense_category_id),
|
||||||
|
key idx_project_budget_category (expense_category_id)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='项目分科目预算';
|
||||||
|
|
||||||
|
create table if not exists project_fund_ledger (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
ledger_no varchar(64) not null comment '流水号',
|
||||||
|
project_id bigint(20) not null comment '项目ID',
|
||||||
|
expense_category_id bigint(20) not null comment '经费科目ID',
|
||||||
|
biz_type varchar(32) not null comment '业务类型',
|
||||||
|
biz_id bigint(20) default null comment '业务主键',
|
||||||
|
biz_no varchar(64) default null comment '业务单号',
|
||||||
|
budget_delta decimal(18,2) not null default 0.00 comment '预算变化额',
|
||||||
|
committed_delta decimal(18,2) not null default 0.00 comment '占用变化额',
|
||||||
|
spent_delta decimal(18,2) not null default 0.00 comment '支出变化额',
|
||||||
|
balance_budget decimal(18,2) not null default 0.00 comment '变更后预算额',
|
||||||
|
balance_committed decimal(18,2) not null default 0.00 comment '变更后占用额',
|
||||||
|
balance_spent decimal(18,2) not null default 0.00 comment '变更后支出额',
|
||||||
|
idempotency_key varchar(128) default null comment '幂等键',
|
||||||
|
operator varchar(64) default null comment '操作人',
|
||||||
|
remark varchar(500) default null comment '说明',
|
||||||
|
create_time datetime not null default current_timestamp comment '创建时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_project_fund_ledger_no (ledger_no),
|
||||||
|
unique key uk_project_fund_idempotency (idempotency_key),
|
||||||
|
key idx_project_fund_project_category (project_id, expense_category_id, create_time),
|
||||||
|
key idx_project_fund_biz (biz_type, biz_id)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='项目经费不可变流水';
|
||||||
|
|
||||||
|
create table if not exists stock_lot (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
lot_no varchar(64) not null comment '库存批次号',
|
||||||
|
area_id bigint(20) not null comment '库区ID',
|
||||||
|
product_id bigint(20) not null comment '产品ID',
|
||||||
|
source_project_id bigint(20) not null comment '采购或来源项目ID',
|
||||||
|
expense_category_id bigint(20) default null comment '来源经费科目ID',
|
||||||
|
purchase_request_id bigint(20) default null comment '采购申请ID',
|
||||||
|
arrival_id bigint(20) default null comment '到货ID',
|
||||||
|
in_record_id bigint(20) default null comment '入库记录ID',
|
||||||
|
received_quantity decimal(16,3) not null comment '入库数量',
|
||||||
|
available_quantity decimal(16,3) not null comment '可用数量',
|
||||||
|
unit_cost decimal(18,2) not null default 0.00 comment '单位成本',
|
||||||
|
lot_status tinyint(1) not null default 0 comment '状态:0可用 1用尽 2冻结',
|
||||||
|
remark varchar(500) default null comment '备注',
|
||||||
|
create_by varchar(64) default null comment '创建人',
|
||||||
|
create_time datetime default null comment '创建时间',
|
||||||
|
update_by varchar(64) default null comment '更新人',
|
||||||
|
update_time datetime default null comment '更新时间',
|
||||||
|
primary key (id),
|
||||||
|
unique key uk_stock_lot_no (lot_no),
|
||||||
|
unique key uk_stock_lot_in_record (in_record_id),
|
||||||
|
key idx_stock_lot_available (area_id, product_id, lot_status, create_time),
|
||||||
|
key idx_stock_lot_source_project (source_project_id),
|
||||||
|
key idx_stock_lot_purchase (purchase_request_id)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='库存来源批次';
|
||||||
|
|
||||||
|
create table if not exists out_record_lot (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
out_record_id bigint(20) not null comment '出库记录ID',
|
||||||
|
stock_lot_id bigint(20) not null comment '库存批次ID',
|
||||||
|
source_project_id bigint(20) not null comment '来源项目ID',
|
||||||
|
usage_project_id bigint(20) not null comment '使用项目ID',
|
||||||
|
quantity decimal(16,3) not null comment '出库数量',
|
||||||
|
unit_cost decimal(18,2) not null default 0.00 comment '单位成本',
|
||||||
|
amount decimal(18,2) not null default 0.00 comment '使用成本',
|
||||||
|
create_time datetime not null default current_timestamp comment '创建时间',
|
||||||
|
primary key (id),
|
||||||
|
key idx_out_record_lot_out (out_record_id),
|
||||||
|
key idx_out_record_lot_stock (stock_lot_id),
|
||||||
|
key idx_out_record_lot_project_flow (source_project_id, usage_project_id)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='出库库存批次分摊';
|
||||||
|
|
||||||
|
create table if not exists ai_command (
|
||||||
|
id bigint(20) not null auto_increment comment '主键',
|
||||||
|
user_id bigint(20) not null comment '发起用户ID',
|
||||||
|
username varchar(64) not null comment '发起账号',
|
||||||
|
action varchar(64) not null comment '动作',
|
||||||
|
required_permission varchar(128) default null comment '所需权限',
|
||||||
|
request_text varchar(1000) default null comment '原始文本',
|
||||||
|
plan_json json not null comment '服务器确认的执行计划',
|
||||||
|
result_json json default null comment '执行结果',
|
||||||
|
command_status varchar(20) not null comment 'WAIT_CONFIRM/EXECUTING/SUCCEEDED/REJECTED/EXPIRED/FAILED',
|
||||||
|
expires_at datetime not null comment '过期时间',
|
||||||
|
confirmed_at datetime default null comment '人工确认时间',
|
||||||
|
executed_at datetime default null comment '执行完成时间',
|
||||||
|
error_message varchar(1000) default null comment '失败原因',
|
||||||
|
create_time datetime not null default current_timestamp comment '创建时间',
|
||||||
|
update_time datetime default null comment '更新时间',
|
||||||
|
primary key (id),
|
||||||
|
key idx_ai_command_user_status (user_id, command_status, create_time),
|
||||||
|
key idx_ai_command_expire (expires_at)
|
||||||
|
) engine=innodb default charset=utf8mb4 comment='AI 待确认业务指令';
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_request' and column_name = 'project_id');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_request add column project_id bigint(20) default null comment ''采购项目ID'' after purchase_batch_id',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_request' and column_name = 'expense_category_id');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_request add column expense_category_id bigint(20) default null comment ''经费科目ID'' after project_id',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_request' and column_name = 'estimated_unit_price');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_request add column estimated_unit_price decimal(18,2) default null comment ''预计含税单价'' after quantity',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_request' and column_name = 'estimated_amount');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_request add column estimated_amount decimal(18,2) default null comment ''预计总金额'' after estimated_unit_price',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'purchase_request'
|
||||||
|
and index_name = 'idx_purchase_request_project');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table purchase_request add key idx_purchase_request_project (project_id, expense_category_id)',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_arrival' and column_name = 'actual_unit_price');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_arrival add column actual_unit_price decimal(18,2) default null comment ''本批实际含税单价'' after arrival_quantity',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'purchase_arrival' and column_name = 'actual_amount');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table purchase_arrival add column actual_amount decimal(18,2) default null comment ''本批实际金额'' after actual_unit_price',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'in_record' and column_name = 'source_project_id');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table in_record add column source_project_id bigint(20) default null comment ''来源项目ID'' after arrival_id',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'in_record' and column_name = 'expense_category_id');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table in_record add column expense_category_id bigint(20) default null comment ''来源经费科目ID'' after source_project_id',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'in_record'
|
||||||
|
and index_name = 'idx_in_record_source_project');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table in_record add key idx_in_record_source_project (source_project_id)',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'out_record' and column_name = 'usage_project_id');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table out_record add column usage_project_id bigint(20) default null comment ''使用项目ID'' after product_id',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'out_record' and column_name = 'cross_project_flag');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table out_record add column cross_project_flag tinyint(1) not null default 0 comment ''是否跨项目使用'' after source_no',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @column_exists = (select count(*) from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = 'out_record' and column_name = 'cross_project_reason');
|
||||||
|
set @ddl = if(@column_exists = 0,
|
||||||
|
'alter table out_record add column cross_project_reason varchar(500) default null comment ''跨项目使用原因'' after cross_project_flag',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
set @idx_exists = (select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'out_record'
|
||||||
|
and index_name = 'idx_out_record_usage_project');
|
||||||
|
set @ddl = if(@idx_exists = 0,
|
||||||
|
'alter table out_record add key idx_out_record_usage_project (usage_project_id)',
|
||||||
|
'select 1');
|
||||||
|
prepare stmt from @ddl; execute stmt; deallocate prepare stmt;
|
||||||
|
|
||||||
|
insert into project_info(project_code, project_name, manager_id, project_status, currency, remark, create_by, create_time)
|
||||||
|
select 'LEGACY', '历史待归属项目', 1, 3, 'CNY', '升级前数据统一归入该项目,后续不得虚构原始归属', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from project_info where project_code = 'LEGACY');
|
||||||
|
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
select 'MATERIAL', '材料费', 10, 0, '材料及耗材', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from expense_category where category_code = 'MATERIAL');
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
select 'EQUIPMENT', '设备费', 20, 0, '仪器设备购置', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from expense_category where category_code = 'EQUIPMENT');
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
select 'TRIAL', '试制费', 30, 0, '试制及加工费用', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from expense_category where category_code = 'TRIAL');
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
select 'OTHER', '其他费用', 90, 0, '其他项目费用', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from expense_category where category_code = 'OTHER');
|
||||||
|
insert into expense_category(category_code, category_name, sort_order, status, remark, create_by, create_time)
|
||||||
|
select 'LEGACY', '历史待归属', 99, 0, '升级前无法确认的经费科目', 'migration', sysdate()
|
||||||
|
where not exists (select 1 from expense_category where category_code = 'LEGACY');
|
||||||
|
|
||||||
|
set @legacy_project_id = (select id from project_info where project_code = 'LEGACY' limit 1);
|
||||||
|
set @legacy_category_id = (select id from expense_category where category_code = 'LEGACY' limit 1);
|
||||||
|
|
||||||
|
insert ignore into project_member(project_id, user_id, member_role, create_by, create_time)
|
||||||
|
values(@legacy_project_id, 1, 'MANAGER', 'migration', sysdate());
|
||||||
|
|
||||||
|
update purchase_request
|
||||||
|
set project_id = @legacy_project_id,
|
||||||
|
expense_category_id = @legacy_category_id,
|
||||||
|
estimated_unit_price = coalesce(estimated_unit_price, 0),
|
||||||
|
estimated_amount = coalesce(estimated_amount, 0)
|
||||||
|
where project_id is null;
|
||||||
|
|
||||||
|
update in_record
|
||||||
|
set source_project_id = @legacy_project_id,
|
||||||
|
expense_category_id = @legacy_category_id
|
||||||
|
where source_project_id is null;
|
||||||
|
|
||||||
|
update out_record
|
||||||
|
set usage_project_id = @legacy_project_id
|
||||||
|
where usage_project_id is null;
|
||||||
|
|
||||||
|
insert ignore into stock_lot(
|
||||||
|
lot_no, area_id, product_id, source_project_id, expense_category_id,
|
||||||
|
received_quantity, available_quantity, unit_cost, lot_status, remark, create_by, create_time
|
||||||
|
)
|
||||||
|
select concat('LEGACY-', si.id), si.area_id, si.product_id, @legacy_project_id, @legacy_category_id,
|
||||||
|
si.stock_num, si.stock_num, 0,
|
||||||
|
case when si.stock_num > 0 then 0 else 1 end,
|
||||||
|
'由升级时库存汇总生成,原采购来源未知', 'migration', coalesce(si.create_time, sysdate())
|
||||||
|
from stock_info si
|
||||||
|
where not exists (select 1 from stock_lot sl where sl.lot_no = concat('LEGACY-', si.id));
|
||||||
|
|
||||||
|
-- 项目管理菜单及权限。固定使用 2100-2113,当前项目菜单最大编号为 2042。
|
||||||
|
insert into sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name,
|
||||||
|
is_frame, is_cache, menu_type, visible, status, perms, icon,
|
||||||
|
create_by, create_time, update_by, update_time, remark)
|
||||||
|
select 2100, '项目管理', 0, 7, 'project', null, '', '', 1, 0, 'M', '0', '0', '', 'money',
|
||||||
|
'admin', sysdate(), '', null, '项目、经费和跨项目使用管理'
|
||||||
|
where not exists (select 1 from sys_menu where menu_id = 2100);
|
||||||
|
|
||||||
|
insert into sys_menu values
|
||||||
|
(2101, '项目与经费', 2100, 1, 'info', 'project/info/index', '', '', 1, 0, 'C', '0', '0', 'project:project:list', 'tree', 'admin', sysdate(), '', null, '项目及经费预算管理'),
|
||||||
|
(2102, '项目查询', 2101, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:query', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2103, '项目新增', 2101, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:add', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2104, '项目修改', 2101, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:edit', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2105, '项目删除', 2101, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:remove', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2106, '项目成员维护', 2101, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:member', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2107, '项目经费查询', 2101, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:budget:list', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2108, '项目经费维护', 2101, 7, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:budget:edit', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2109, '经费科目维护', 2101, 8, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:category:edit', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2110, '项目总览权限', 2101, 9, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:project:manage', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2111, '项目物资使用', 2101, 10, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:inventory:use', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2112, '跨项目使用', 2101, 11, '#', '', '', '', 1, 0, 'F', '0', '0', 'project:inventory:cross-use', '#', 'admin', sysdate(), '', null, ''),
|
||||||
|
(2113, 'AI助手使用', 2101, 12, '#', '', '', '', 1, 0, 'F', '1', '0', 'ai:assistant:use', '#', 'admin', sysdate(), '', null, '所有正常角色默认拥有,仅控制入口,具体动作仍校验业务权限')
|
||||||
|
on duplicate key update menu_id = values(menu_id);
|
||||||
|
|
||||||
|
-- 所有现有正常角色均可进入 AI 助手;业务查询和写入仍由原仓库权限控制。
|
||||||
|
insert ignore into sys_role_menu(role_id, menu_id)
|
||||||
|
select role_id, 2113 from sys_role where status = '0';
|
||||||
|
|
||||||
|
-- 延续现有业务角色能力:采购和库存角色可查看自己参与的项目及经费;原有出库角色获得项目物资使用权限。
|
||||||
|
insert ignore into sys_role_menu(role_id, menu_id)
|
||||||
|
select distinct role_id, target_menu_id
|
||||||
|
from (
|
||||||
|
select rm.role_id, targets.target_menu_id
|
||||||
|
from sys_role_menu rm
|
||||||
|
join sys_menu source_menu on source_menu.menu_id = rm.menu_id
|
||||||
|
join (
|
||||||
|
select 2100 target_menu_id union all select 2101 union all select 2102 union all select 2107
|
||||||
|
) targets
|
||||||
|
where source_menu.perms in ('warehouse:purchase:add', 'warehouse:purchase:manage', 'warehouse:stock:list')
|
||||||
|
) role_project_menu;
|
||||||
|
|
||||||
|
insert ignore into sys_role_menu(role_id, menu_id)
|
||||||
|
select distinct rm.role_id, 2111
|
||||||
|
from sys_role_menu rm
|
||||||
|
join sys_menu m on m.menu_id = rm.menu_id
|
||||||
|
where m.perms in ('warehouse:stock:out', 'warehouse:out:add');
|
||||||
@ -74,6 +74,7 @@ create table if not exists purchase_arrival (
|
|||||||
primary key (id),
|
primary key (id),
|
||||||
unique key uk_purchase_arrival_no (arrival_no),
|
unique key uk_purchase_arrival_no (arrival_no),
|
||||||
key idx_purchase_arrival_request (purchase_request_id),
|
key idx_purchase_arrival_request (purchase_request_id),
|
||||||
|
key idx_purchase_arrival_request_status (purchase_request_id, acceptance_status),
|
||||||
key idx_purchase_arrival_status (acceptance_status)
|
key idx_purchase_arrival_status (acceptance_status)
|
||||||
) engine=innodb default charset=utf8mb4 comment='采购分批到货及验收记录';
|
) engine=innodb default charset=utf8mb4 comment='采购分批到货及验收记录';
|
||||||
|
|
||||||
@ -117,6 +118,20 @@ prepare arrival_revoke_stmt from @arrival_revoke_sql;
|
|||||||
execute arrival_revoke_stmt;
|
execute arrival_revoke_stmt;
|
||||||
deallocate prepare arrival_revoke_stmt;
|
deallocate prepare arrival_revoke_stmt;
|
||||||
|
|
||||||
|
set @purchase_arrival_request_status_index = (
|
||||||
|
select count(*) from information_schema.statistics
|
||||||
|
where table_schema = database() and table_name = 'purchase_arrival'
|
||||||
|
and index_name = 'idx_purchase_arrival_request_status'
|
||||||
|
);
|
||||||
|
set @purchase_arrival_request_status_sql = if(
|
||||||
|
@purchase_arrival_request_status_index = 0,
|
||||||
|
'alter table purchase_arrival add key idx_purchase_arrival_request_status (purchase_request_id, acceptance_status)',
|
||||||
|
'select 1'
|
||||||
|
);
|
||||||
|
prepare purchase_arrival_request_status_stmt from @purchase_arrival_request_status_sql;
|
||||||
|
execute purchase_arrival_request_status_stmt;
|
||||||
|
deallocate prepare purchase_arrival_request_status_stmt;
|
||||||
|
|
||||||
alter table purchase_request modify column purchase_status tinyint(1) not null default 0
|
alter table purchase_request modify column purchase_status tinyint(1) not null default 0
|
||||||
comment '状态:0待采购 1已下单 2采购中 3部分到货 4已到齐 5已取消';
|
comment '状态:0待采购 1已下单 2采购中 3部分到货 4已到齐 5已取消';
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user