add /manual-in/batch for gao product
This commit is contained in:
+97
-74
@@ -1,34 +1,6 @@
|
|||||||
# the name by which the project can be referenced within Serena
|
# the name by which the project can be referenced within Serena/when chatting with the LLM.
|
||||||
project_name: "TimiServerAPI"
|
project_name: "TimiServerAPI"
|
||||||
|
|
||||||
|
|
||||||
# list of languages for which language servers are started; choose from:
|
|
||||||
# al bash clojure cpp csharp
|
|
||||||
# csharp_omnisharp dart elixir elm erlang
|
|
||||||
# fortran fsharp go groovy haskell
|
|
||||||
# haxe java julia kotlin lua
|
|
||||||
# markdown
|
|
||||||
# matlab nix pascal perl php
|
|
||||||
# php_phpactor powershell python python_jedi r
|
|
||||||
# rego ruby ruby_solargraph rust scala
|
|
||||||
# swift terraform toml typescript typescript_vts
|
|
||||||
# vue yaml zig
|
|
||||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
|
||||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
|
||||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
|
||||||
# Note:
|
|
||||||
# - For C, use cpp
|
|
||||||
# - For JavaScript, use typescript
|
|
||||||
# - For Free Pascal/Lazarus, use pascal
|
|
||||||
# Special requirements:
|
|
||||||
# Some languages require additional setup/installations.
|
|
||||||
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
|
||||||
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
|
||||||
# The first language is the default language and the respective language server will be used as a fallback.
|
|
||||||
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
|
||||||
languages:
|
|
||||||
- java
|
|
||||||
|
|
||||||
# the encoding used by text files in the project
|
# the encoding used by text files in the project
|
||||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
encoding: "utf-8"
|
encoding: "utf-8"
|
||||||
@@ -50,12 +22,19 @@ ignore_all_files_in_gitignore: true
|
|||||||
|
|
||||||
# advanced configuration option allowing to configure language server-specific options.
|
# advanced configuration option allowing to configure language server-specific options.
|
||||||
# Maps the language key to the options.
|
# Maps the language key to the options.
|
||||||
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
# The settings are considered only if the project is trusted (see global configuration to define trusted projects).
|
||||||
# No documentation on options means no options are available.
|
# See https://oraios.github.io/serena/02-usage/050_configuration.html#language-server-specific-settings
|
||||||
ls_specific_settings: {}
|
ls_specific_settings: {}
|
||||||
|
|
||||||
# list of additional paths to ignore in this project.
|
# list of additional paths to ignore in this project.
|
||||||
# Same syntax as gitignore, so you can use * and **.
|
# Same syntax as gitignore, so you can use * and **.
|
||||||
|
# Important: quote patterns that start with `*`, otherwise YAML treats them as aliases.
|
||||||
|
# Example:
|
||||||
|
# ignored_paths:
|
||||||
|
# - "examples/**"
|
||||||
|
# - ".worktrees/**"
|
||||||
|
# - "**/bin/**"
|
||||||
|
# - "**/obj/**"
|
||||||
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||||
ignored_paths: []
|
ignored_paths: []
|
||||||
|
|
||||||
@@ -66,54 +45,17 @@ read_only: false
|
|||||||
|
|
||||||
# list of tool names to exclude.
|
# list of tool names to exclude.
|
||||||
# This extends the existing exclusions (e.g. from the global configuration)
|
# This extends the existing exclusions (e.g. from the global configuration)
|
||||||
#
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
# Below is the complete list of tools for convenience.
|
|
||||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
|
||||||
# execute `uv run scripts/print_tool_overview.py`.
|
|
||||||
#
|
|
||||||
# * `activate_project`: Activates a project based on the project name or path.
|
|
||||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
|
||||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
|
||||||
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
|
|
||||||
# for example by saying that the information retrieved from a memory file is no longer correct
|
|
||||||
# or no longer relevant for the project.
|
|
||||||
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
|
||||||
# * `execute_shell_command`: Executes a shell command.
|
|
||||||
# * `find_file`: Finds files in the given relative paths
|
|
||||||
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
|
||||||
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
|
||||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
|
||||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
|
||||||
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
|
||||||
# for clients that do not read the initial instructions when the MCP server is connected.
|
|
||||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
|
||||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
|
||||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
|
||||||
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
|
||||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
|
||||||
# * `read_file`: Reads a file within the project directory.
|
|
||||||
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
|
|
||||||
# is relevant to the current task. You can infer whether the information
|
|
||||||
# is relevant from the memory file name.
|
|
||||||
# You should not read the same memory file multiple times in the same conversation.
|
|
||||||
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
|
||||||
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
|
||||||
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
|
||||||
# For JB, we use a separate tool.
|
|
||||||
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
|
||||||
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
|
||||||
# * `safe_delete_symbol`:
|
|
||||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
|
||||||
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
|
|
||||||
# The memory name should be meaningful.
|
|
||||||
excluded_tools: []
|
excluded_tools: []
|
||||||
|
|
||||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||||
# This extends the existing inclusions (e.g. from the global configuration).
|
# This extends the existing inclusions (e.g. from the global configuration).
|
||||||
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
included_optional_tools: []
|
included_optional_tools: []
|
||||||
|
|
||||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||||
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
fixed_tools: []
|
fixed_tools: []
|
||||||
|
|
||||||
# list of mode names to that are always to be included in the set of active modes
|
# list of mode names to that are always to be included in the set of active modes
|
||||||
@@ -124,11 +66,14 @@ fixed_tools: []
|
|||||||
# Set this to a list of mode names to always include the respective modes for this project.
|
# Set this to a list of mode names to always include the respective modes for this project.
|
||||||
base_modes:
|
base_modes:
|
||||||
|
|
||||||
# list of mode names that are to be activated by default.
|
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
||||||
# The full set of modes to be activated is base_modes + default_modes.
|
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||||
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
||||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||||
|
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
||||||
|
# for this project.
|
||||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||||
|
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||||
default_modes:
|
default_modes:
|
||||||
|
|
||||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
@@ -152,3 +97,81 @@ read_only_memory_patterns: []
|
|||||||
# Extends the list from the global configuration, merging the two lists.
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
# Example: ["_archive/.*", "_episodes/.*"]
|
# Example: ["_archive/.*", "_episodes/.*"]
|
||||||
ignored_memory_patterns: []
|
ignored_memory_patterns: []
|
||||||
|
|
||||||
|
# list of language servers to start when using the LSP backend; choose from:
|
||||||
|
# ada al angular ansible bash
|
||||||
|
# bsl clojure cpp cpp_ccls crystal
|
||||||
|
# csharp csharp_omnisharp cue dart deno
|
||||||
|
# elixir elm erlang fortran fsharp
|
||||||
|
# gdscript gleam go groovy haskell
|
||||||
|
# haxe hlsl html java json
|
||||||
|
# julia kotlin latex lean4 lua
|
||||||
|
# luau markdown matlab msl nextflow
|
||||||
|
# nix ocaml pascal perl php
|
||||||
|
# php_phpactor php_phpantom powershell python python_basedpyright
|
||||||
|
# python_jedi python_pyrefly python_ty qml r
|
||||||
|
# rego ruby ruby_solargraph rust scala
|
||||||
|
# scss solidity svelte swift systemverilog
|
||||||
|
# terraform toml typescript typescript_vts vue
|
||||||
|
# wolfram yaml zig
|
||||||
|
# (This list may be outdated; generated with scripts/print_language_list.py;
|
||||||
|
# For the current list, see values of the LanguageServerId enum here:
|
||||||
|
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
|
||||||
|
# For some languages, there are several alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
|
||||||
|
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
|
||||||
|
# - For Deno projects, use deno (serves the same .ts/.js files as typescript; requires the deno CLI on PATH)
|
||||||
|
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
|
||||||
|
# - For Free Pascal/Lazarus, use pascal
|
||||||
|
# Special requirements:
|
||||||
|
# Some language servers require additional setup/installations.
|
||||||
|
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||||
|
# When using multiple language servers, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language server is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
language_servers:
|
||||||
|
- java
|
||||||
|
|
||||||
|
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
||||||
|
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||||
|
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||||
|
added_modes:
|
||||||
|
|
||||||
|
# list of workspace folder paths (LSP backend only).
|
||||||
|
# These folders will be used to build up Serena's symbol index.
|
||||||
|
# Paths must be within the project root and should thus be relative to the project root.
|
||||||
|
# Furthermore, the paths should not be filtered by ignore settings.
|
||||||
|
# Default setting: The entire project root folder (".") is considered.
|
||||||
|
# In (large) monorepos, this can be used to index only subfolders of the project root, e.g.
|
||||||
|
# ls_workspace_folders:
|
||||||
|
# - "./subproject1"
|
||||||
|
# - "./subproject2"
|
||||||
|
ls_workspace_folders:
|
||||||
|
- .
|
||||||
|
|
||||||
|
# list of additional workspace folder paths for cross-package reference support.
|
||||||
|
# Paths can be absolute or relative to the project root.
|
||||||
|
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
|
||||||
|
# symbols and references across package boundaries, but these folders are not indexed by Serena,
|
||||||
|
# i.e. the respective symbols will not be found using Serena's symbol search tools.
|
||||||
|
# Example:
|
||||||
|
# additional_workspace_folders:
|
||||||
|
# - ../sibling-package
|
||||||
|
# - ../shared-lib
|
||||||
|
ls_additional_workspace_folders: []
|
||||||
|
|
||||||
|
# optional shell command to run before the language backend (LSP or JetBrains) is initialised.
|
||||||
|
# the command runs in the project root directory and is only executed if the project is trusted
|
||||||
|
# (see trusted_project_path_patterns in the global configuration).
|
||||||
|
# serena waits for the command to exit: a non-zero exit code is logged as an error but does not
|
||||||
|
# abort activation. a per-project timeout (activation_command_timeout, default 180s) is the safety
|
||||||
|
# backstop for non-terminating commands; on expiry the process is killed and activation continues.
|
||||||
|
# example: activation_command: "npx nx run-many -t build"
|
||||||
|
activation_command:
|
||||||
|
|
||||||
|
# maximum time in seconds to wait for activation_command to complete before killing it (default 180s).
|
||||||
|
# must be a positive number.
|
||||||
|
activation_command_timeout: 180.0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.imyeyu.api.modules.gao.service.GaoProductStockRecordService;
|
|||||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockBatchRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockBatchRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
||||||
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualInBatchRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
||||||
@@ -93,6 +94,21 @@ public class GaoProductStockController {
|
|||||||
return stockService.manualIn(normalize(request));
|
return stockService.manualIn(normalize(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 批量手工入库
|
||||||
|
///
|
||||||
|
/// @param request 批量手工入库请求
|
||||||
|
/// @return 更新后的库存列表
|
||||||
|
/// @throws TimiException 请求或库存校验失败
|
||||||
|
@AOPLog
|
||||||
|
@RequestRateLimit
|
||||||
|
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
|
||||||
|
@PostMapping("/manual-in/batch")
|
||||||
|
public List<GaoProductStock> manualInBatch(@RequestBody GaoProductStockManualInBatchRequest request) {
|
||||||
|
TimiException.required(request, "not found stock batch request");
|
||||||
|
request.setStoreId(storeScopeService.resolveStoreId(request.getStoreId()));
|
||||||
|
return stockService.manualInBatch(request);
|
||||||
|
}
|
||||||
|
|
||||||
@AOPLog
|
@AOPLog
|
||||||
@RequestRateLimit
|
@RequestRateLimit
|
||||||
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
|
@RequireGaoPermission(GaoPermissionCode.PRODUCT_STOCK_CREATE)
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import com.imyeyu.api.modules.common.entity.Attachment;
|
|||||||
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
import com.imyeyu.api.modules.gao.entity.GaoProductStock;
|
||||||
import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
|
import com.imyeyu.api.modules.gao.entity.GaoProductStockRecord;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
||||||
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualInBatchRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
import com.imyeyu.api.modules.gao.vo.GaoStockChangeItem;
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -36,6 +38,13 @@ public interface GaoProductStockService {
|
|||||||
/// 手工入库
|
/// 手工入库
|
||||||
GaoProductStock manualIn(GaoProductStockChangeRequest request);
|
GaoProductStock manualIn(GaoProductStockChangeRequest request);
|
||||||
|
|
||||||
|
/// 批量手工入库
|
||||||
|
///
|
||||||
|
/// @param request 批量手工入库请求
|
||||||
|
/// @return 更新后的库存列表
|
||||||
|
/// @throws TimiException 请求或库存校验失败
|
||||||
|
List<GaoProductStock> manualInBatch(GaoProductStockManualInBatchRequest request);
|
||||||
|
|
||||||
/// 手工出库
|
/// 手工出库
|
||||||
GaoProductStock manualOut(GaoProductStockChangeRequest request);
|
GaoProductStock manualOut(GaoProductStockChangeRequest request);
|
||||||
|
|
||||||
|
|||||||
+139
-4
@@ -16,6 +16,7 @@ import com.imyeyu.api.modules.gao.service.GaoProductStockService;
|
|||||||
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
import com.imyeyu.api.modules.gao.service.GaoStoreScopeService;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
import com.imyeyu.api.modules.gao.vo.GaoProductSkuUnitSelection;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockChangeRequest;
|
||||||
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualInBatchRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockManualRevokeRequest;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockPage;
|
||||||
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
import com.imyeyu.api.modules.gao.vo.GaoProductStockProductView;
|
||||||
@@ -150,6 +151,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
return change(request, GaoProductStockRecord.OperationType.INITIAL_IN);
|
return change(request, GaoProductStockRecord.OperationType.INITIAL_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 批量手工入库
|
||||||
|
///
|
||||||
|
/// @param request 批量手工入库请求
|
||||||
|
/// @return 更新后的库存列表
|
||||||
|
/// @throws TimiException 请求或库存校验失败
|
||||||
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_IN)
|
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_IN)
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
@@ -157,6 +163,54 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
return change(request, GaoProductStockRecord.OperationType.MANUAL_IN);
|
return change(request, GaoProductStockRecord.OperationType.MANUAL_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_IN)
|
||||||
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
|
@Override
|
||||||
|
public List<GaoProductStock> manualInBatch(GaoProductStockManualInBatchRequest request) {
|
||||||
|
TimiException.required(request, "not found stock batch request");
|
||||||
|
String requestNo = requiredText(request.getRequestNo(), "not found requestNo");
|
||||||
|
String storeId = storeScopeService.resolveStoreId(request.getStoreId());
|
||||||
|
TimiException.required(storeId, "not found storeId");
|
||||||
|
String remark = request.getRemark();
|
||||||
|
TimiException.requiredTrue(hasText(remark), "库存操作必须填写备注");
|
||||||
|
TimiException.requiredTrue(request.getItemList() != null && !request.getItemList().isEmpty(), "批量入库至少需要一个 SKU");
|
||||||
|
List<GaoProductStockManualInBatchRequest.Item> requestItemList = new ArrayList<>(request.getItemList());
|
||||||
|
for (GaoProductStockManualInBatchRequest.Item item : requestItemList) {
|
||||||
|
TimiException.required(item, "库存变更项不能为空");
|
||||||
|
item.setSkuId(requiredText(item.getSkuId(), "not found skuId"));
|
||||||
|
item.setSkuUnitId(requiredText(item.getSkuUnitId(), "not found skuUnitId"));
|
||||||
|
}
|
||||||
|
requestItemList.sort(Comparator.comparing(GaoProductStockManualInBatchRequest.Item::getSkuId));
|
||||||
|
Set<String> skuIdSet = new HashSet<>();
|
||||||
|
List<GaoStockChangeItem> changeItemList = new ArrayList<>();
|
||||||
|
for (GaoProductStockManualInBatchRequest.Item item : requestItemList) {
|
||||||
|
TimiException.requiredTrue(skuIdSet.add(item.getSkuId()), "同一库存操作不能重复 SKU");
|
||||||
|
GaoProductSkuUnitSelection selection = skuService.getSkuUnit(storeId, item.getSkuId(), item.getSkuUnitId(), true);
|
||||||
|
GaoProductSku sku = selection.getSku();
|
||||||
|
GaoProduct product = selection.getProduct();
|
||||||
|
GaoProductSkuUnit unit = selection.getUnit();
|
||||||
|
TimiException.requiredTrue(Boolean.TRUE.equals(sku.getStockEnabled()), "该 SKU 未启用库存管理");
|
||||||
|
BigDecimal quantity = validateQuantity(sku, item.getQuantity());
|
||||||
|
BigDecimal baseQuantity = quantity.multiply(unit.getBaseQuantityPerUnit());
|
||||||
|
GaoStockChangeItem changeItem = GaoStockChangeItem.of(product, sku, unit, quantity, baseQuantity);
|
||||||
|
changeItem.setSkuSpecSnapshot(skuService.getSkuSpecSnapshot(sku));
|
||||||
|
changeItemList.add(changeItem);
|
||||||
|
}
|
||||||
|
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, requestNo);
|
||||||
|
if (!existingList.isEmpty()) {
|
||||||
|
verifyExistingManualInBatch(existingList, changeItemList, remark);
|
||||||
|
List<GaoProductStock> result = new ArrayList<>();
|
||||||
|
for (GaoStockChangeItem item : changeItemList) {
|
||||||
|
result.add(detail(storeId, item.getSkuId()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Map<String, List<Attachment>> itemAttachmentMap = prepareManualInBatchAttachments(requestItemList, request.getAttachmentList());
|
||||||
|
return executeInternal(storeId, changeItemList.getFirst().getSkuId(), changeItemList, requestNo,
|
||||||
|
userLoginService.getRequireLoginUserId(), GaoProductStockRecord.OperationType.MANUAL_IN, Time.now(), remark, null,
|
||||||
|
GaoProductStockRecord.BizType.PRODUCT_STOCK, itemAttachmentMap, true);
|
||||||
|
}
|
||||||
|
|
||||||
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_OUT)
|
@OperationAudit(AuditOperation.GAO_PRODUCT_STOCK_MANUAL_OUT)
|
||||||
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
@Transactional(TimiServerDBConfig.ROLLBACKER)
|
||||||
@Override
|
@Override
|
||||||
@@ -391,6 +445,13 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
||||||
String operatorUserId, GaoProductStockRecord.OperationType operationType, Long occurredAt, String remark, List<Attachment> attachmentList,
|
String operatorUserId, GaoProductStockRecord.OperationType operationType, Long occurredAt, String remark, List<Attachment> attachmentList,
|
||||||
GaoProductStockRecord.BizType bizType) {
|
GaoProductStockRecord.BizType bizType) {
|
||||||
|
return executeInternal(storeId, bizId, sourceList, requestNo, operatorUserId, operationType, occurredAt, remark, attachmentList,
|
||||||
|
bizType, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GaoProductStock> executeInternal(String storeId, String bizId, List<GaoStockChangeItem> sourceList, String requestNo,
|
||||||
|
String operatorUserId, GaoProductStockRecord.OperationType operationType, Long occurredAt, String remark, List<Attachment> attachmentList,
|
||||||
|
GaoProductStockRecord.BizType bizType, Map<String, List<Attachment>> itemAttachmentMap, boolean skuAsBizId) {
|
||||||
TimiException.required(storeId, "not found storeId");
|
TimiException.required(storeId, "not found storeId");
|
||||||
TimiException.required(bizId, "not found stock bizId");
|
TimiException.required(bizId, "not found stock bizId");
|
||||||
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品");
|
TimiException.requiredTrue(sourceList != null && !sourceList.isEmpty(), "库存变更至少需要一个产品");
|
||||||
@@ -407,7 +468,11 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
}
|
}
|
||||||
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
|
List<GaoProductStockRecord> existingList = recordService.listByRequestNo(storeId, normalizedRequestNo);
|
||||||
if (!existingList.isEmpty()) {
|
if (!existingList.isEmpty()) {
|
||||||
verifyExistingList(existingList, itemList, operationType, bizId);
|
if (skuAsBizId) {
|
||||||
|
verifyExistingManualInBatch(existingList, itemList, remark);
|
||||||
|
} else {
|
||||||
|
verifyExistingList(existingList, itemList, operationType, bizId);
|
||||||
|
}
|
||||||
List<GaoProductStock> result = new ArrayList<>();
|
List<GaoProductStock> result = new ArrayList<>();
|
||||||
for (GaoStockChangeItem item : itemList) {
|
for (GaoStockChangeItem item : itemList) {
|
||||||
result.add(detail(storeId, item.getSkuId()));
|
result.add(detail(storeId, item.getSkuId()));
|
||||||
@@ -447,14 +512,16 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
record.setReservedBefore(reservedBefore);
|
record.setReservedBefore(reservedBefore);
|
||||||
record.setReservedAfter(reservedAfter);
|
record.setReservedAfter(reservedAfter);
|
||||||
record.setBizType(bizType);
|
record.setBizType(bizType);
|
||||||
record.setBizId(bizId);
|
record.setBizId(skuAsBizId ? item.getSkuId() : bizId);
|
||||||
record.setSourceRecordId(item.getSourceRecordId());
|
record.setSourceRecordId(item.getSourceRecordId());
|
||||||
record.setRequestNo(normalizedRequestNo);
|
record.setRequestNo(normalizedRequestNo);
|
||||||
record.setOperatorUserId(operatorUserId);
|
record.setOperatorUserId(operatorUserId);
|
||||||
record.setOccurredAt(TimiJava.defaultIfNull(occurredAt, Time.now()));
|
record.setOccurredAt(occurredAt == null ? Time.now() : occurredAt);
|
||||||
record.setRemark(remark);
|
record.setRemark(remark);
|
||||||
|
List<Attachment> recordAttachmentList = itemAttachmentMap == null
|
||||||
|
? attachmentList : itemAttachmentMap.get(item.getSkuId());
|
||||||
recordService.createOperationRecord(record,
|
recordService.createOperationRecord(record,
|
||||||
operationType == GaoProductStockRecord.OperationType.ORDER_OUT ? null : attachmentList);
|
operationType == GaoProductStockRecord.OperationType.ORDER_OUT ? null : recordAttachmentList);
|
||||||
if (operationType == GaoProductStockRecord.OperationType.ORDER_OUT) {
|
if (operationType == GaoProductStockRecord.OperationType.ORDER_OUT) {
|
||||||
copyAttachments(record.getId(), attachmentList);
|
copyAttachments(record.getId(), attachmentList);
|
||||||
}
|
}
|
||||||
@@ -562,6 +629,74 @@ public class GaoProductStockServiceImplement implements GaoProductStockService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 校验批量手工入库幂等请求
|
||||||
|
///
|
||||||
|
/// @param existingList 已存在的库存流水
|
||||||
|
/// @param itemList 请求中的入库项
|
||||||
|
/// @param remark 批次备注
|
||||||
|
/// @throws TimiException 请求内容与已存在流水不一致
|
||||||
|
private void verifyExistingManualInBatch(List<GaoProductStockRecord> existingList, List<GaoStockChangeItem> itemList, String remark) {
|
||||||
|
TimiException.requiredTrue(existingList.size() == itemList.size(), "requestNo 对应库存操作内容不一致");
|
||||||
|
Map<String, GaoProductStockRecord> recordMap = new HashMap<>();
|
||||||
|
for (GaoProductStockRecord record : existingList) {
|
||||||
|
TimiException.requiredTrue(record.getOperationType() == GaoProductStockRecord.OperationType.MANUAL_IN
|
||||||
|
&& GaoProductStockRecord.BizType.PRODUCT_STOCK == record.getBizType()
|
||||||
|
&& record.getSkuId() != null && record.getSkuId().equals(record.getBizId()), "requestNo 已被其他库存操作使用");
|
||||||
|
TimiException.requiredTrue(recordMap.put(record.getSkuId(), record) == null, "requestNo 对应库存操作内容不一致");
|
||||||
|
}
|
||||||
|
TimiException.requiredTrue(recordMap.size() == itemList.size(), "requestNo 对应库存操作内容不一致");
|
||||||
|
for (GaoStockChangeItem item : itemList) {
|
||||||
|
GaoProductStockRecord record = recordMap.get(item.getSkuId());
|
||||||
|
TimiException.required(record, "requestNo 对应库存操作内容不一致");
|
||||||
|
TimiException.requiredTrue(item.getSkuUnitId().equals(record.getSkuUnitId())
|
||||||
|
&& record.getQuantity() != null && item.getQuantity().compareTo(record.getQuantity()) == 0
|
||||||
|
&& record.getBaseQuantity() != null && item.getBaseQuantity().compareTo(record.getBaseQuantity()) == 0
|
||||||
|
&& sameText(record.getRemark(), remark), "requestNo 对应库存操作内容不一致");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 准备批量入库附件并复制复用的全局临时附件
|
||||||
|
///
|
||||||
|
/// @param itemList 排序后的入库项
|
||||||
|
/// @param globalAttachmentList 全局附件列表
|
||||||
|
/// @return 按 SKU 分组的临时附件列表
|
||||||
|
/// @throws TimiException 附件无效或缺失
|
||||||
|
private Map<String, List<Attachment>> prepareManualInBatchAttachments(List<GaoProductStockManualInBatchRequest.Item> itemList,
|
||||||
|
List<Attachment> globalAttachmentList) {
|
||||||
|
Map<String, List<Attachment>> result = new HashMap<>();
|
||||||
|
Set<String> usedTempFileIdSet = new HashSet<>();
|
||||||
|
for (GaoProductStockManualInBatchRequest.Item item : itemList) {
|
||||||
|
boolean useGlobalAttachments = item.getAttachmentList() == null || item.getAttachmentList().isEmpty();
|
||||||
|
List<Attachment> sourceAttachmentList = useGlobalAttachments ? globalAttachmentList : item.getAttachmentList();
|
||||||
|
TimiException.requiredTrue(sourceAttachmentList != null && !sourceAttachmentList.isEmpty(), "每个 SKU 至少需要一个附件");
|
||||||
|
List<Attachment> attachmentList = new ArrayList<>();
|
||||||
|
for (Attachment source : sourceAttachmentList) {
|
||||||
|
TimiException.required(source, "入库附件不能为空");
|
||||||
|
String tempFileId = requiredText(source.getTempFileId(), "入库附件 tempFileId 不能为空");
|
||||||
|
Attachment tempFile = attachmentService.get(tempFileId);
|
||||||
|
TimiException.required(tempFile, "not found temp file");
|
||||||
|
TimiException.requiredTrue(tempFile.getBizType() == Attachment.BizType.TEMP_FILE, "入库附件必须是临时文件");
|
||||||
|
if (!usedTempFileIdSet.add(tempFileId)) {
|
||||||
|
TimiException.requiredTrue(useGlobalAttachments, "同一临时附件不能用于多个入库项");
|
||||||
|
Attachment clone = attachmentService.clone(tempFileId);
|
||||||
|
TimiException.required(clone, "not found cloned temp file");
|
||||||
|
TimiException.requiredTrue(clone.getBizType() == Attachment.BizType.TEMP_FILE, "克隆附件必须是临时文件");
|
||||||
|
tempFileId = requiredText(clone.getId(), "克隆附件 ID 不能为空");
|
||||||
|
TimiException.requiredTrue(usedTempFileIdSet.add(tempFileId), "克隆附件 ID 已被使用");
|
||||||
|
}
|
||||||
|
Attachment attachment = new Attachment();
|
||||||
|
attachment.setTempFileId(tempFileId);
|
||||||
|
attachment.setAttachType(source.getAttachType());
|
||||||
|
attachment.setName(source.getName());
|
||||||
|
attachment.setMetadata(source.getMetadata());
|
||||||
|
attachmentList.add(attachment);
|
||||||
|
}
|
||||||
|
TimiException.requiredTrue(!attachmentList.isEmpty(), "每个 SKU 至少需要一个附件");
|
||||||
|
result.put(item.getSkuId(), attachmentList);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void fillBalance(GaoProductStock stock) {
|
private void fillBalance(GaoProductStock stock) {
|
||||||
stock.setOnHandQuantity(decimal(stock.getOnHandQuantity()));
|
stock.setOnHandQuantity(decimal(stock.getOnHandQuantity()));
|
||||||
stock.setReservedQuantity(decimal(stock.getReservedQuantity()));
|
stock.setReservedQuantity(decimal(stock.getReservedQuantity()));
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.imyeyu.api.modules.gao.vo;
|
||||||
|
|
||||||
|
import com.imyeyu.api.modules.common.entity.Attachment;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// GAO 产品批量手工入库请求
|
||||||
|
///
|
||||||
|
/// @author Codex
|
||||||
|
/// @since 2026-10-13
|
||||||
|
@Data
|
||||||
|
public class GaoProductStockManualInBatchRequest {
|
||||||
|
|
||||||
|
/// 客户端幂等请求号
|
||||||
|
private String requestNo;
|
||||||
|
|
||||||
|
/// 门店 ID
|
||||||
|
private String storeId;
|
||||||
|
|
||||||
|
/// 批次备注
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/// 全局附件列表
|
||||||
|
private List<Attachment> attachmentList;
|
||||||
|
|
||||||
|
/// 批量入库项
|
||||||
|
private List<Item> itemList;
|
||||||
|
|
||||||
|
/// 批量手工入库项
|
||||||
|
@Data
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
/// SKU ID
|
||||||
|
private String skuId;
|
||||||
|
|
||||||
|
/// SKU 单位 ID
|
||||||
|
private String skuUnitId;
|
||||||
|
|
||||||
|
/// 入库数量
|
||||||
|
private BigDecimal quantity;
|
||||||
|
|
||||||
|
/// SKU 自有附件列表
|
||||||
|
private List<Attachment> attachmentList;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user