liujie
2026-08-26 7ffd310e52ef6135197379f0ac2c4522780e53b9
修改bug
12个文件已添加
24个文件已修改
1989 ■■■■■ 已修改文件
.spec-workflow/templates/design-template.md 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/product-template.md 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/requirements-template.md 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/structure-template.md 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/tasks-template.md 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/tech-template.md 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/user-templates/README.md 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TErpGoodsController.java 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TErpGoodsTypeController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TOrderController.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/PdfUtils.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-test.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TSysAppUserController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/WxPayController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/resources/application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/constant/GoodsTypeConstants.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/GoodsSortUpdateDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpClinicGoodsAddDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpClinicGoodsUpdateDto.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpGoodsAddDto.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpGoodsInfoSaveDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/export/TErpGoodsDetailExport.java 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/importExcel/TErpFixedGoodsExcel.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TErpGoods.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/TErpGoodsAdminQuery.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/TErpGoodsQuery.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TErpGoodsTypeService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCrmSupplierServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpGoodsServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpGoodsTypeServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpProcurementServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/util/ObsUploadUtil.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/wxPay/config/WxConfig.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TErpGoodsMapper.xml 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260821_goods_builtin_type_fields.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.spec-workflow/templates/design-template.md
New file
@@ -0,0 +1,96 @@
# Design Document
## Overview
[High-level description of the feature and its place in the overall system]
## Steering Document Alignment
### Technical Standards (tech.md)
[How the design follows documented technical patterns and standards]
### Project Structure (structure.md)
[How the implementation will follow project organization conventions]
## Code Reuse Analysis
[What existing code will be leveraged, extended, or integrated with this feature]
### Existing Components to Leverage
- **[Component/Utility Name]**: [How it will be used]
- **[Service/Helper Name]**: [How it will be extended]
### Integration Points
- **[Existing System/API]**: [How the new feature will integrate]
- **[Database/Storage]**: [How data will connect to existing schemas]
## Architecture
[Describe the overall architecture and design patterns used]
### Modular Design Principles
- **Single File Responsibility**: Each file should handle one specific concern or domain
- **Component Isolation**: Create small, focused components rather than large monolithic files
- **Service Layer Separation**: Separate data access, business logic, and presentation layers
- **Utility Modularity**: Break utilities into focused, single-purpose modules
```mermaid
graph TD
    A[Component A] --> B[Component B]
    B --> C[Component C]
```
## Components and Interfaces
### Component 1
- **Purpose:** [What this component does]
- **Interfaces:** [Public methods/APIs]
- **Dependencies:** [What it depends on]
- **Reuses:** [Existing components/utilities it builds upon]
### Component 2
- **Purpose:** [What this component does]
- **Interfaces:** [Public methods/APIs]
- **Dependencies:** [What it depends on]
- **Reuses:** [Existing components/utilities it builds upon]
## Data Models
### Model 1
```
[Define the structure of Model1 in your language]
- id: [unique identifier type]
- name: [string/text type]
- [Additional properties as needed]
```
### Model 2
```
[Define the structure of Model2 in your language]
- id: [unique identifier type]
- [Additional properties as needed]
```
## Error Handling
### Error Scenarios
1. **Scenario 1:** [Description]
   - **Handling:** [How to handle]
   - **User Impact:** [What user sees]
2. **Scenario 2:** [Description]
   - **Handling:** [How to handle]
   - **User Impact:** [What user sees]
## Testing Strategy
### Unit Testing
- [Unit testing approach]
- [Key components to test]
### Integration Testing
- [Integration testing approach]
- [Key flows to test]
### End-to-End Testing
- [E2E testing approach]
- [User scenarios to test]
.spec-workflow/templates/product-template.md
New file
@@ -0,0 +1,51 @@
# Product Overview
## Product Purpose
[Describe the core purpose of this product/project. What problem does it solve?]
## Target Users
[Who are the primary users of this product? What are their needs and pain points?]
## Key Features
[List the main features that deliver value to users]
1. **Feature 1**: [Description]
2. **Feature 2**: [Description]
3. **Feature 3**: [Description]
## Business Objectives
[What are the business goals this product aims to achieve?]
- [Objective 1]
- [Objective 2]
- [Objective 3]
## Success Metrics
[How will we measure the success of this product?]
- [Metric 1]: [Target]
- [Metric 2]: [Target]
- [Metric 3]: [Target]
## Product Principles
[Core principles that guide product decisions]
1. **[Principle 1]**: [Explanation]
2. **[Principle 2]**: [Explanation]
3. **[Principle 3]**: [Explanation]
## Monitoring & Visibility (if applicable)
[How do users track progress and monitor the system?]
- **Dashboard Type**: [e.g., Web-based, CLI, Desktop app]
- **Real-time Updates**: [e.g., WebSocket, polling, push notifications]
- **Key Metrics Displayed**: [What information is most important to surface]
- **Sharing Capabilities**: [e.g., read-only links, exports, reports]
## Future Vision
[Where do we see this product evolving in the future?]
### Potential Enhancements
- **Remote Access**: [e.g., Tunnel features for sharing dashboards with stakeholders]
- **Analytics**: [e.g., Historical trends, performance metrics]
- **Collaboration**: [e.g., Multi-user support, commenting]
.spec-workflow/templates/requirements-template.md
New file
@@ -0,0 +1,50 @@
# Requirements Document
## Introduction
[Provide a brief overview of the feature, its purpose, and its value to users]
## Alignment with Product Vision
[Explain how this feature supports the goals outlined in product.md]
## Requirements
### Requirement 1
**User Story:** As a [role], I want [feature], so that [benefit]
#### Acceptance Criteria
1. WHEN [event] THEN [system] SHALL [response]
2. IF [precondition] THEN [system] SHALL [response]
3. WHEN [event] AND [condition] THEN [system] SHALL [response]
### Requirement 2
**User Story:** As a [role], I want [feature], so that [benefit]
#### Acceptance Criteria
1. WHEN [event] THEN [system] SHALL [response]
2. IF [precondition] THEN [system] SHALL [response]
## Non-Functional Requirements
### Code Architecture and Modularity
- **Single Responsibility Principle**: Each file should have a single, well-defined purpose
- **Modular Design**: Components, utilities, and services should be isolated and reusable
- **Dependency Management**: Minimize interdependencies between modules
- **Clear Interfaces**: Define clean contracts between components and layers
### Performance
- [Performance requirements]
### Security
- [Security requirements]
### Reliability
- [Reliability requirements]
### Usability
- [Usability requirements]
.spec-workflow/templates/structure-template.md
New file
@@ -0,0 +1,145 @@
# Project Structure
## Directory Organization
```
[Define your project's directory structure. Examples below - adapt to your project type]
Example for a library/package:
project-root/
├── src/                    # Source code
├── tests/                  # Test files
├── docs/                   # Documentation
├── examples/               # Usage examples
└── [build/dist/out]        # Build output
Example for an application:
project-root/
├── [src/app/lib]           # Main source code
├── [assets/resources]      # Static resources
├── [config/settings]       # Configuration
├── [scripts/tools]         # Build/utility scripts
└── [tests/spec]            # Test files
Common patterns:
- Group by feature/module
- Group by layer (UI, business logic, data)
- Group by type (models, controllers, views)
- Flat structure for simple projects
```
## Naming Conventions
### Files
- **Components/Modules**: [e.g., `PascalCase`, `snake_case`, `kebab-case`]
- **Services/Handlers**: [e.g., `UserService`, `user_service`, `user-service`]
- **Utilities/Helpers**: [e.g., `dateUtils`, `date_utils`, `date-utils`]
- **Tests**: [e.g., `[filename]_test`, `[filename].test`, `[filename]Test`]
### Code
- **Classes/Types**: [e.g., `PascalCase`, `CamelCase`, `snake_case`]
- **Functions/Methods**: [e.g., `camelCase`, `snake_case`, `PascalCase`]
- **Constants**: [e.g., `UPPER_SNAKE_CASE`, `SCREAMING_CASE`, `PascalCase`]
- **Variables**: [e.g., `camelCase`, `snake_case`, `lowercase`]
## Import Patterns
### Import Order
1. External dependencies
2. Internal modules
3. Relative imports
4. Style imports
### Module/Package Organization
```
[Describe your project's import/include patterns]
Examples:
- Absolute imports from project root
- Relative imports within modules
- Package/namespace organization
- Dependency management approach
```
## Code Structure Patterns
[Define common patterns for organizing code within files. Below are examples - choose what applies to your project]
### Module/Class Organization
```
Example patterns:
1. Imports/includes/dependencies
2. Constants and configuration
3. Type/interface definitions
4. Main implementation
5. Helper/utility functions
6. Exports/public API
```
### Function/Method Organization
```
Example patterns:
- Input validation first
- Core logic in the middle
- Error handling throughout
- Clear return points
```
### File Organization Principles
```
Choose what works for your project:
- One class/module per file
- Related functionality grouped together
- Public API at the top/bottom
- Implementation details hidden
```
## Code Organization Principles
1. **Single Responsibility**: Each file should have one clear purpose
2. **Modularity**: Code should be organized into reusable modules
3. **Testability**: Structure code to be easily testable
4. **Consistency**: Follow patterns established in the codebase
## Module Boundaries
[Define how different parts of your project interact and maintain separation of concerns]
Examples of boundary patterns:
- **Core vs Plugins**: Core functionality vs extensible plugins
- **Public API vs Internal**: What's exposed vs implementation details
- **Platform-specific vs Cross-platform**: OS-specific code isolation
- **Stable vs Experimental**: Production code vs experimental features
- **Dependencies direction**: Which modules can depend on which
## Code Size Guidelines
[Define your project's guidelines for file and function sizes]
Suggested guidelines:
- **File size**: [Define maximum lines per file]
- **Function/Method size**: [Define maximum lines per function]
- **Class/Module complexity**: [Define complexity limits]
- **Nesting depth**: [Maximum nesting levels]
## Dashboard/Monitoring Structure (if applicable)
[How dashboard or monitoring components are organized]
### Example Structure:
```
src/
└── dashboard/          # Self-contained dashboard subsystem
    ├── server/        # Backend server components
    ├── client/        # Frontend assets
    ├── shared/        # Shared types/utilities
    └── public/        # Static assets
```
### Separation of Concerns
- Dashboard isolated from core business logic
- Own CLI entry point for independent operation
- Minimal dependencies on main application
- Can be disabled without affecting core functionality
## Documentation Standards
- All public APIs must have documentation
- Complex logic should include inline comments
- README files for major modules
- Follow language-specific documentation conventions
.spec-workflow/templates/tasks-template.md
New file
@@ -0,0 +1,139 @@
# Tasks Document
- [ ] 1. Create core interfaces in src/types/feature.ts
  - File: src/types/feature.ts
  - Define TypeScript interfaces for feature data structures
  - Extend existing base interfaces from base.ts
  - Purpose: Establish type safety for feature implementation
  - _Leverage: src/types/base.ts_
  - _Requirements: 1.1_
  - _Prompt: Role: TypeScript Developer specializing in type systems and interfaces | Task: Create comprehensive TypeScript interfaces for the feature data structures following requirements 1.1, extending existing base interfaces from src/types/base.ts | Restrictions: Do not modify existing base interfaces, maintain backward compatibility, follow project naming conventions | Success: All interfaces compile without errors, proper inheritance from base types, full type coverage for feature requirements_
- [ ] 2. Create base model class in src/models/FeatureModel.ts
  - File: src/models/FeatureModel.ts
  - Implement base model extending BaseModel class
  - Add validation methods using existing validation utilities
  - Purpose: Provide data layer foundation for feature
  - _Leverage: src/models/BaseModel.ts, src/utils/validation.ts_
  - _Requirements: 2.1_
  - _Prompt: Role: Backend Developer with expertise in Node.js and data modeling | Task: Create a base model class extending BaseModel and implementing validation following requirement 2.1, leveraging existing patterns from src/models/BaseModel.ts and src/utils/validation.ts | Restrictions: Must follow existing model patterns, do not bypass validation utilities, maintain consistent error handling | Success: Model extends BaseModel correctly, validation methods implemented and tested, follows project architecture patterns_
- [ ] 3. Add specific model methods to FeatureModel.ts
  - File: src/models/FeatureModel.ts (continue from task 2)
  - Implement create, update, delete methods
  - Add relationship handling for foreign keys
  - Purpose: Complete model functionality for CRUD operations
  - _Leverage: src/models/BaseModel.ts_
  - _Requirements: 2.2, 2.3_
  - _Prompt: Role: Backend Developer with expertise in ORM and database operations | Task: Implement CRUD methods and relationship handling in FeatureModel.ts following requirements 2.2 and 2.3, extending patterns from src/models/BaseModel.ts | Restrictions: Must maintain transaction integrity, follow existing relationship patterns, do not duplicate base model functionality | Success: All CRUD operations work correctly, relationships are properly handled, database operations are atomic and efficient_
- [ ] 4. Create model unit tests in tests/models/FeatureModel.test.ts
  - File: tests/models/FeatureModel.test.ts
  - Write tests for model validation and CRUD methods
  - Use existing test utilities and fixtures
  - Purpose: Ensure model reliability and catch regressions
  - _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
  - _Requirements: 2.1, 2.2_
  - _Prompt: Role: QA Engineer with expertise in unit testing and Jest/Mocha frameworks | Task: Create comprehensive unit tests for FeatureModel validation and CRUD methods covering requirements 2.1 and 2.2, using existing test utilities from tests/helpers/testUtils.ts and fixtures from tests/fixtures/data.ts | Restrictions: Must test both success and failure scenarios, do not test external dependencies directly, maintain test isolation | Success: All model methods are tested with good coverage, edge cases covered, tests run independently and consistently_
- [ ] 5. Create service interface in src/services/IFeatureService.ts
  - File: src/services/IFeatureService.ts
  - Define service contract with method signatures
  - Extend base service interface patterns
  - Purpose: Establish service layer contract for dependency injection
  - _Leverage: src/services/IBaseService.ts_
  - _Requirements: 3.1_
  - _Prompt: Role: Software Architect specializing in service-oriented architecture and TypeScript interfaces | Task: Design service interface contract following requirement 3.1, extending base service patterns from src/services/IBaseService.ts for dependency injection | Restrictions: Must maintain interface segregation principle, do not expose internal implementation details, ensure contract compatibility with DI container | Success: Interface is well-defined with clear method signatures, extends base service appropriately, supports all required service operations_
- [ ] 6. Implement feature service in src/services/FeatureService.ts
  - File: src/services/FeatureService.ts
  - Create concrete service implementation using FeatureModel
  - Add error handling with existing error utilities
  - Purpose: Provide business logic layer for feature operations
  - _Leverage: src/services/BaseService.ts, src/utils/errorHandler.ts, src/models/FeatureModel.ts_
  - _Requirements: 3.2_
  - _Prompt: Role: Backend Developer with expertise in service layer architecture and business logic | Task: Implement concrete FeatureService following requirement 3.2, using FeatureModel and extending BaseService patterns with proper error handling from src/utils/errorHandler.ts | Restrictions: Must implement interface contract exactly, do not bypass model validation, maintain separation of concerns from data layer | Success: Service implements all interface methods correctly, robust error handling implemented, business logic is well-encapsulated and testable_
- [ ] 7. Add service dependency injection in src/utils/di.ts
  - File: src/utils/di.ts (modify existing)
  - Register FeatureService in dependency injection container
  - Configure service lifetime and dependencies
  - Purpose: Enable service injection throughout application
  - _Leverage: existing DI configuration in src/utils/di.ts_
  - _Requirements: 3.1_
  - _Prompt: Role: DevOps Engineer with expertise in dependency injection and IoC containers | Task: Register FeatureService in DI container following requirement 3.1, configuring appropriate lifetime and dependencies using existing patterns from src/utils/di.ts | Restrictions: Must follow existing DI container patterns, do not create circular dependencies, maintain service resolution efficiency | Success: FeatureService is properly registered and resolvable, dependencies are correctly configured, service lifetime is appropriate for use case_
- [ ] 8. Create service unit tests in tests/services/FeatureService.test.ts
  - File: tests/services/FeatureService.test.ts
  - Write tests for service methods with mocked dependencies
  - Test error handling scenarios
  - Purpose: Ensure service reliability and proper error handling
  - _Leverage: tests/helpers/testUtils.ts, tests/mocks/modelMocks.ts_
  - _Requirements: 3.2, 3.3_
  - _Prompt: Role: QA Engineer with expertise in service testing and mocking frameworks | Task: Create comprehensive unit tests for FeatureService methods covering requirements 3.2 and 3.3, using mocked dependencies from tests/mocks/modelMocks.ts and test utilities | Restrictions: Must mock all external dependencies, test business logic in isolation, do not test framework code | Success: All service methods tested with proper mocking, error scenarios covered, tests verify business logic correctness and error handling_
- [ ] 4. Create API endpoints
  - Design API structure
  - _Leverage: src/api/baseApi.ts, src/utils/apiUtils.ts_
  - _Requirements: 4.0_
  - _Prompt: Role: API Architect specializing in RESTful design and Express.js | Task: Design comprehensive API structure following requirement 4.0, leveraging existing patterns from src/api/baseApi.ts and utilities from src/utils/apiUtils.ts | Restrictions: Must follow REST conventions, maintain API versioning compatibility, do not expose internal data structures directly | Success: API structure is well-designed and documented, follows existing patterns, supports all required operations with proper HTTP methods and status codes_
- [ ] 4.1 Set up routing and middleware
  - Configure application routes
  - Add authentication middleware
  - Set up error handling middleware
  - _Leverage: src/middleware/auth.ts, src/middleware/errorHandler.ts_
  - _Requirements: 4.1_
  - _Prompt: Role: Backend Developer with expertise in Express.js middleware and routing | Task: Configure application routes and middleware following requirement 4.1, integrating authentication from src/middleware/auth.ts and error handling from src/middleware/errorHandler.ts | Restrictions: Must maintain middleware order, do not bypass security middleware, ensure proper error propagation | Success: Routes are properly configured with correct middleware chain, authentication works correctly, errors are handled gracefully throughout the request lifecycle_
- [ ] 4.2 Implement CRUD endpoints
  - Create API endpoints
  - Add request validation
  - Write API integration tests
  - _Leverage: src/controllers/BaseController.ts, src/utils/validation.ts_
  - _Requirements: 4.2, 4.3_
  - _Prompt: Role: Full-stack Developer with expertise in API development and validation | Task: Implement CRUD endpoints following requirements 4.2 and 4.3, extending BaseController patterns and using validation utilities from src/utils/validation.ts | Restrictions: Must validate all inputs, follow existing controller patterns, ensure proper HTTP status codes and responses | Success: All CRUD operations work correctly, request validation prevents invalid data, integration tests pass and cover all endpoints_
- [ ] 5. Add frontend components
  - Plan component architecture
  - _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
  - _Requirements: 5.0_
  - _Prompt: Role: Frontend Architect with expertise in React component design and architecture | Task: Plan comprehensive component architecture following requirement 5.0, leveraging base patterns from src/components/BaseComponent.tsx and theme system from src/styles/theme.ts | Restrictions: Must follow existing component patterns, maintain design system consistency, ensure component reusability | Success: Architecture is well-planned and documented, components are properly organized, follows existing patterns and theme system_
- [ ] 5.1 Create base UI components
  - Set up component structure
  - Implement reusable components
  - Add styling and theming
  - _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
  - _Requirements: 5.1_
  - _Prompt: Role: Frontend Developer specializing in React and component architecture | Task: Create reusable UI components following requirement 5.1, extending BaseComponent patterns and using existing theme system from src/styles/theme.ts | Restrictions: Must use existing theme variables, follow component composition patterns, ensure accessibility compliance | Success: Components are reusable and properly themed, follow existing architecture, accessible and responsive_
- [ ] 5.2 Implement feature-specific components
  - Create feature components
  - Add state management
  - Connect to API endpoints
  - _Leverage: src/hooks/useApi.ts, src/components/BaseComponent.tsx_
  - _Requirements: 5.2, 5.3_
  - _Prompt: Role: React Developer with expertise in state management and API integration | Task: Implement feature-specific components following requirements 5.2 and 5.3, using API hooks from src/hooks/useApi.ts and extending BaseComponent patterns | Restrictions: Must use existing state management patterns, handle loading and error states properly, maintain component performance | Success: Components are fully functional with proper state management, API integration works smoothly, user experience is responsive and intuitive_
- [ ] 6. Integration and testing
  - Plan integration approach
  - _Leverage: src/utils/integrationUtils.ts, tests/helpers/testUtils.ts_
  - _Requirements: 6.0_
  - _Prompt: Role: Integration Engineer with expertise in system integration and testing strategies | Task: Plan comprehensive integration approach following requirement 6.0, leveraging integration utilities from src/utils/integrationUtils.ts and test helpers | Restrictions: Must consider all system components, ensure proper test coverage, maintain integration test reliability | Success: Integration plan is comprehensive and feasible, all system components work together correctly, integration points are well-tested_
- [ ] 6.1 Write end-to-end tests
  - Set up E2E testing framework
  - Write user journey tests
  - Add test automation
  - _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
  - _Requirements: All_
  - _Prompt: Role: QA Automation Engineer with expertise in E2E testing and test frameworks like Cypress or Playwright | Task: Implement comprehensive end-to-end tests covering all requirements, setting up testing framework and user journey tests using test utilities and fixtures | Restrictions: Must test real user workflows, ensure tests are maintainable and reliable, do not test implementation details | Success: E2E tests cover all critical user journeys, tests run reliably in CI/CD pipeline, user experience is validated from end-to-end_
- [ ] 6.2 Final integration and cleanup
  - Integrate all components
  - Fix any integration issues
  - Clean up code and documentation
  - _Leverage: src/utils/cleanup.ts, docs/templates/_
  - _Requirements: All_
  - _Prompt: Role: Senior Developer with expertise in code quality and system integration | Task: Complete final integration of all components and perform comprehensive cleanup covering all requirements, using cleanup utilities and documentation templates | Restrictions: Must not break existing functionality, ensure code quality standards are met, maintain documentation consistency | Success: All components are fully integrated and working together, code is clean and well-documented, system meets all requirements and quality standards_
.spec-workflow/templates/tech-template.md
New file
@@ -0,0 +1,99 @@
# Technology Stack
## Project Type
[Describe what kind of project this is: web application, CLI tool, desktop application, mobile app, library, API service, embedded system, game, etc.]
## Core Technologies
### Primary Language(s)
- **Language**: [e.g., Python 3.11, Go 1.21, TypeScript, Rust, C++]
- **Runtime/Compiler**: [if applicable]
- **Language-specific tools**: [package managers, build tools, etc.]
### Key Dependencies/Libraries
[List the main libraries and frameworks your project depends on]
- **[Library/Framework name]**: [Purpose and version]
- **[Library/Framework name]**: [Purpose and version]
### Application Architecture
[Describe how your application is structured - this could be MVC, event-driven, plugin-based, client-server, standalone, microservices, monolithic, etc.]
### Data Storage (if applicable)
- **Primary storage**: [e.g., PostgreSQL, files, in-memory, cloud storage]
- **Caching**: [e.g., Redis, in-memory, disk cache]
- **Data formats**: [e.g., JSON, Protocol Buffers, XML, binary]
### External Integrations (if applicable)
- **APIs**: [External services you integrate with]
- **Protocols**: [e.g., HTTP/REST, gRPC, WebSocket, TCP/IP]
- **Authentication**: [e.g., OAuth, API keys, certificates]
### Monitoring & Dashboard Technologies (if applicable)
- **Dashboard Framework**: [e.g., React, Vue, vanilla JS, terminal UI]
- **Real-time Communication**: [e.g., WebSocket, Server-Sent Events, polling]
- **Visualization Libraries**: [e.g., Chart.js, D3, terminal graphs]
- **State Management**: [e.g., Redux, Vuex, file system as source of truth]
## Development Environment
### Build & Development Tools
- **Build System**: [e.g., Make, CMake, Gradle, npm scripts, cargo]
- **Package Management**: [e.g., pip, npm, cargo, go mod, apt, brew]
- **Development workflow**: [e.g., hot reload, watch mode, REPL]
### Code Quality Tools
- **Static Analysis**: [Tools for code quality and correctness]
- **Formatting**: [Code style enforcement tools]
- **Testing Framework**: [Unit, integration, and/or end-to-end testing tools]
- **Documentation**: [Documentation generation tools]
### Version Control & Collaboration
- **VCS**: [e.g., Git, Mercurial, SVN]
- **Branching Strategy**: [e.g., Git Flow, GitHub Flow, trunk-based]
- **Code Review Process**: [How code reviews are conducted]
### Dashboard Development (if applicable)
- **Live Reload**: [e.g., Hot module replacement, file watchers]
- **Port Management**: [e.g., Dynamic allocation, configurable ports]
- **Multi-Instance Support**: [e.g., Running multiple dashboards simultaneously]
## Deployment & Distribution (if applicable)
- **Target Platform(s)**: [Where/how the project runs: cloud, on-premise, desktop, mobile, embedded]
- **Distribution Method**: [How users get your software: download, package manager, app store, SaaS]
- **Installation Requirements**: [Prerequisites, system requirements]
- **Update Mechanism**: [How updates are delivered]
## Technical Requirements & Constraints
### Performance Requirements
- [e.g., response time, throughput, memory usage, startup time]
- [Specific benchmarks or targets]
### Compatibility Requirements
- **Platform Support**: [Operating systems, architectures, versions]
- **Dependency Versions**: [Minimum/maximum versions of dependencies]
- **Standards Compliance**: [Industry standards, protocols, specifications]
### Security & Compliance
- **Security Requirements**: [Authentication, encryption, data protection]
- **Compliance Standards**: [GDPR, HIPAA, SOC2, etc. if applicable]
- **Threat Model**: [Key security considerations]
### Scalability & Reliability
- **Expected Load**: [Users, requests, data volume]
- **Availability Requirements**: [Uptime targets, disaster recovery]
- **Growth Projections**: [How the system needs to scale]
## Technical Decisions & Rationale
[Document key architectural and technology choices]
### Decision Log
1. **[Technology/Pattern Choice]**: [Why this was chosen, alternatives considered]
2. **[Architecture Decision]**: [Rationale, trade-offs accepted]
3. **[Tool/Library Selection]**: [Reasoning, evaluation criteria]
## Known Limitations
[Document any technical debt, limitations, or areas for improvement]
- [Limitation 1]: [Impact and potential future solutions]
- [Limitation 2]: [Why it exists and when it might be addressed]
.spec-workflow/user-templates/README.md
New file
@@ -0,0 +1,64 @@
# User Templates
This directory allows you to create custom templates that override the default Spec Workflow templates.
## How to Use Custom Templates
1. **Create your custom template file** in this directory with the exact same name as the default template you want to override:
   - `requirements-template.md` - Override requirements document template
   - `design-template.md` - Override design document template
   - `tasks-template.md` - Override tasks document template
   - `product-template.md` - Override product steering template
   - `tech-template.md` - Override tech steering template
   - `structure-template.md` - Override structure steering template
2. **Template Loading Priority**:
   - The system first checks this `user-templates/` directory
   - If a matching template is found here, it will be used
   - Otherwise, the default template from `templates/` will be used
## Example Custom Template
To create a custom requirements template:
1. Create a file named `requirements-template.md` in this directory
2. Add your custom structure, for example:
```markdown
# Requirements Document
## Executive Summary
[Your custom section]
## Business Requirements
[Your custom structure]
## Technical Requirements
[Your custom fields]
## Custom Sections
[Add any sections specific to your workflow]
```
## Template Variables
Templates can include placeholders that will be replaced when documents are created:
- `{{projectName}}` - The name of your project
- `{{featureName}}` - The name of the feature being specified
- `{{date}}` - The current date
- `{{author}}` - The document author
## Best Practices
1. **Start from defaults**: Copy a default template from `../templates/` as a starting point
2. **Keep structure consistent**: Maintain similar section headers for tool compatibility
3. **Document changes**: Add comments explaining why sections were added/modified
4. **Version control**: Track your custom templates in version control
5. **Test thoroughly**: Ensure custom templates work with the spec workflow tools
## Notes
- Custom templates are project-specific and not included in the package distribution
- The `templates/` directory contains the default templates which are updated with each version
- Your custom templates in this directory are preserved during updates
- If a custom template has errors, the system will fall back to the default template
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TErpGoodsController.java
@@ -23,8 +23,11 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.WebUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.constant.GoodsTypeConstants;
import com.ruoyi.system.dto.*;
import com.ruoyi.system.export.TErpGoodsDetailExport;
import com.ruoyi.system.importExcel.TErpCloudGoodsExcel;
import com.ruoyi.system.importExcel.TErpFixedGoodsExcel;
import com.ruoyi.system.importExcel.TErpGoodsExcel;
import com.ruoyi.system.model.*;
import com.ruoyi.system.query.PageAddListQuery;
@@ -52,6 +55,7 @@
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * <p>
@@ -148,6 +152,23 @@
        }
        return R.ok();
    }
    @ApiOperation(value = "平台设置商品排序")
    @PostMapping(value = "/setGoodsSort")
    public R<?> setGoodsSort(@RequestBody GoodsSortUpdateDto dto) {
        SysUser user = tokenService.getLoginUser().getUser();
        Integer roleType = user.getRoleType();
        if (roleType == 1) {
            // 平台
            TErpGoods goods = erpGoodsService.getById(dto.getId());
            if(goods!=null){
                goods.setSort(dto.getSort());
                erpGoodsService.updateById(goods);
            }
        }
        return R.ok();
    }
    @ApiOperation(value = "平台审核商品内容")
@@ -272,6 +293,7 @@
        }
        Integer roleType = user.getRoleType();
        TErpGoods goods = new TErpGoods();
        BeanUtils.copyProperties(dto, goods);
        if (roleType == 4) {
            // 供应商
            TCrmSupplier crmSupplier = crmSupplierService.getOne(Wrappers.lambdaQuery(TCrmSupplier.class)
@@ -305,9 +327,9 @@
        goods.setFormulationSpec(dto.getFormulationSpec());
        goods.setPackingSpec(dto.getPackingSpec());
        goods.setGoodsName(dto.getGoodsName());
        goods.setIsPrescriptionDrug(null);
        goods.setIsPrescriptionDrug(dto.getIsPrescriptionDrug());
        goods.setTypeId(dto.getTypeId());
        goods.setGoodsSpec(dto.getFormulationSpec() + "*" + dto.getPackingSpec());
        goods.setGoodsSpec(buildGoodsSpec(dto.getFormulationSpec(), dto.getPackingSpec()));
        return R.ok(erpGoodsService.save(goods));
    }
@@ -319,13 +341,14 @@
        if (erpGoodsService.isExit(dto.getGoodsIdCode(), dto.getQuasiNumber(), null)) {
            return R.fail("erp商品已存在,商品条码重复");
        }
        if (erpGoodsService.isExitOne(dto.getGoodsIdCode(), dto.getQuasiNumber(), null)) {
        if (StringUtils.isNotBlank(dto.getQuasiNumber())
                && erpGoodsService.isExitOne(dto.getGoodsIdCode(), dto.getQuasiNumber(), null)) {
            return R.fail("erp商品已存在,国药准字号重复");
        }
        Integer roleType = user.getRoleType();
        TErpGoods goods = new TErpGoods();
        BeanUtils.copyProperties(dto, goods);
        goods.setGoodsSpec(dto.getFormulationSpec() + "*" + dto.getPackingSpec());
        goods.setGoodsSpec(buildGoodsSpec(dto.getFormulationSpec(), dto.getPackingSpec()));
        if (roleType == 5) {
            // 诊所
            TCrmClinic crmClinic = crmClinicService.getOne(Wrappers.lambdaQuery(TCrmClinic.class)
@@ -341,7 +364,7 @@
        }
        goods.setCreateId(user.getUserId().toString());
        goods.setGoodsIdCode(dto.getGoodsIdCode());
        goods.setGoodsSpec(dto.getFormulationSpec() + "*" + dto.getPackingSpec());
        goods.setGoodsSpec(buildGoodsSpec(dto.getFormulationSpec(), dto.getPackingSpec()));
//        goods.setClinicPurchasePrice(dto.getSalesAmount());
        if (goods.getIsPrescriptionDrug() != null && goods.getIsPrescriptionDrug() == 3) {
            goods.setIsPrescriptionDrug(null);
@@ -376,7 +399,7 @@
        goods.setCreateId(user.getUserId().toString());
        goods.setGoodsIdCode(dto.getGoodsIdCode());
        goods.setId(dto.getId());
        goods.setGoodsSpec(dto.getFormulationSpec() + "*" + dto.getPackingSpec());
        goods.setGoodsSpec(buildGoodsSpec(dto.getFormulationSpec(), dto.getPackingSpec()));
        if (goods.getIsPrescriptionDrug() != null && goods.getIsPrescriptionDrug() == 3) {
            goods.setIsPrescriptionDrug(null);
        }
@@ -580,6 +603,13 @@
        return R.ok(goods);
    }
    private String buildGoodsSpec(String formulationSpec, String packingSpec) {
        if (StringUtils.isNotBlank(formulationSpec) && StringUtils.isNotBlank(packingSpec)) {
            return formulationSpec + "*" + packingSpec;
        }
        return StringUtils.isNotBlank(packingSpec) ? packingSpec : formulationSpec;
    }
    @ApiOperation(value = "商品管理信息导出--导出")
    @PostMapping("/export")
    @Log(title = "商品管理-导出", businessType = BusinessType.EXPORT)
@@ -613,7 +643,12 @@
        }
        List<TErpGoodsVO> pageList = erpGoodsService.listExport(query, user);
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), TErpGoodsVO.class, pageList);
        List<TErpGoodsDetailExport> exportList = pageList.stream().map(goods -> {
            TErpGoodsDetailExport exportItem = new TErpGoodsDetailExport();
            BeanUtils.copyProperties(goods, exportItem);
            return exportItem;
        }).collect(Collectors.toList());
        Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), TErpGoodsDetailExport.class, exportList);
        HttpServletResponse response = WebUtils.response();
        assert response != null;
        response.setContentType("application/vnd.ms-excel");
@@ -827,6 +862,311 @@
    }
    /**
     * 供应商固定商品类型导入。旧商品导入接口保留给非固定类型模板使用。
     */
    @ApiOperation(value = "供应商固定商品类型导入")
    @PostMapping("/importFixedGoodsFile")
    @Log(title = "商品管理-固定类型导入", businessType = BusinessType.IMPORT)
    public AjaxResult importFixedGoodsFile(
            @RequestParam(value = "file") MultipartFile file,
            @RequestParam(value = "typeId") String typeId) {
        SysUser user = tokenService.getLoginUser().getUser();
        if (user == null || user.getRoleType() == null || user.getRoleType() != 4) {
            return AjaxResult.error("仅供应商可以导入固定商品类型");
        }
        int sheetIndex = getFixedGoodsSheetIndex(typeId);
        if (sheetIndex < 0) {
            return AjaxResult.error("商品类型不支持固定模板导入");
        }
        if (file == null || file.isEmpty()) {
            return AjaxResult.error("请选择要导入的文件");
        }
        TCrmSupplier supplier = getCurrentSupplier(user);
        if (supplier == null) {
            return AjaxResult.error("未找到当前供应商信息");
        }
        List<String> errors = new ArrayList<>();
        List<TErpGoods> goodsList = new ArrayList<>();
        Set<String> barcodeSet = new HashSet<>();
        ImportParams params = new ImportParams();
        params.setHeadRows(1);
        params.setStartSheetIndex(sheetIndex);
        params.setSheetNum(1);
        try (InputStream inputStream = file.getInputStream()) {
            List<TErpFixedGoodsExcel> rows = ExcelImportUtil.importExcel(
                    inputStream, TErpFixedGoodsExcel.class, params);
            if (rows == null || rows.isEmpty()) {
                return AjaxResult.error("所选商品类型工作表没有可导入的数据");
            }
            for (int index = 0; index < rows.size(); index++) {
                TErpFixedGoodsExcel row = rows.get(index);
                if (isBlankFixedGoodsRow(row)) {
                    continue;
                }
                String rowName = StringUtils.isBlank(row.getGoodsName())
                        ? "第" + (index + 2) + "行"
                        : row.getGoodsName().trim();
                String validationError = validateFixedGoodsRow(row, typeId);
                if (validationError != null) {
                    errors.add(rowName + ":" + validationError);
                    continue;
                }
                String goodsIdCode = row.getGoodsIdCode().trim();
                if (!barcodeSet.add(goodsIdCode)) {
                    errors.add(rowName + ":商品条码在导入文件中重复");
                    continue;
                }
                if (erpGoodsService.count(new QueryWrapper<TErpGoods>()
                        .eq("goods_id_code", goodsIdCode)) > 0) {
                    errors.add(rowName + ":商品条码已存在");
                    continue;
                }
                try {
                    TErpGoods goods = buildFixedGoods(row, typeId, supplier, user);
                    goodsList.add(goods);
                } catch (IllegalArgumentException e) {
                    errors.add(rowName + ":" + e.getMessage());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("导入文件解析失败,请确认使用固定商品类型模板");
        }
        if (goodsList.isEmpty() && errors.isEmpty()) {
            return AjaxResult.error("所选商品类型工作表没有可导入的数据");
        }
        if (!errors.isEmpty()) {
            return AjaxResult.error("导入失败记录如下:", errors);
        }
        erpGoodsService.saveBatch(goodsList);
        return AjaxResult.success();
    }
    private int getFixedGoodsSheetIndex(String typeId) {
        if (GoodsTypeConstants.DRUG_ID.equals(typeId)) {
            return 0;
        }
        if (GoodsTypeConstants.MEDICAL_DEVICE_ID.equals(typeId)) {
            return 1;
        }
        if (GoodsTypeConstants.FOOD_ID.equals(typeId)) {
            return 2;
        }
        return -1;
    }
    private TCrmSupplier getCurrentSupplier(SysUser user) {
        TCrmSupplier supplier = crmSupplierService.getOne(
                Wrappers.lambdaQuery(TCrmSupplier.class)
                        .eq(TCrmSupplier::getUserId, user.getUserId())
                        .last("LIMIT 1"));
        if (supplier == null) {
            SysUser sysUser = sysUserService.selectUserById(user.getUserId());
            if (sysUser != null && sysUser.getObjectId() != null) {
                supplier = crmSupplierService.getById(sysUser.getObjectId());
            }
        }
        return supplier;
    }
    private String validateFixedGoodsRow(TErpFixedGoodsExcel row, String typeId) {
        if (StringUtils.isBlank(row.getGoodsName())) {
            return "商品名称不能为空";
        }
        if (StringUtils.isBlank(row.getGoodsIdCode())) {
            return "商品条形码不能为空";
        }
        if (StringUtils.isBlank(row.getManufacturer())) {
            return "生产厂家不能为空";
        }
        if (StringUtils.isBlank(row.getWarningInventory())) {
            return "预警库存不能为空";
        }
        if (StringUtils.isBlank(row.getPackingUnitName())) {
            return "包装单位不能为空";
        }
        if (GoodsTypeConstants.DRUG_ID.equals(typeId)) {
            if (StringUtils.isBlank(row.getQuasiNumber())) {
                return "批准文号或国药准字号不能为空";
            }
            if (StringUtils.isBlank(row.getProductType())) {
                return "商品类型不能为空";
            }
            if (StringUtils.isBlank(row.getFormulationSpec())) {
                return "剂型规格不能为空";
            }
            if (StringUtils.isBlank(row.getPackingSpec())) {
                return "包装规格不能为空";
            }
            if (StringUtils.isBlank(row.getEffectivePeriod())) {
                return "有效时间不能为空";
            }
        } else if (GoodsTypeConstants.MEDICAL_DEVICE_ID.equals(typeId)) {
            if (StringUtils.isBlank(row.getDeviceCategory())) {
                return "器械类别不能为空";
            }
            if (StringUtils.isBlank(row.getProductType())) {
                return "商品类型不能为空";
            }
            if (StringUtils.isBlank(row.getProductTechnicalRequirementNumber())) {
                return "产品技术要求编号不能为空";
            }
            if (StringUtils.isBlank(row.getProductionLicenseOrFilingNumber())) {
                return "生产许可证或备案凭证编号不能为空";
            }
            if (StringUtils.isBlank(row.getRegistrantOrFiler())) {
                return "注册人或备案人不能为空";
            }
            if (StringUtils.isBlank(row.getEffectivePeriod())) {
                return "有效时间不能为空";
            }
        } else if (GoodsTypeConstants.FOOD_ID.equals(typeId)) {
            if (StringUtils.isBlank(row.getProductionLicenseNumber())) {
                return "生产许可证号不能为空";
            }
            if (StringUtils.isBlank(row.getProductType())) {
                return "商品类型不能为空";
            }
            if (StringUtils.isBlank(row.getPackingSpec())) {
                return "包装规格不能为空";
            }
            if (StringUtils.isBlank(row.getProductCategory())) {
                return "产品类别不能为空";
            }
        }
        return null;
    }
    private boolean isBlankFixedGoodsRow(TErpFixedGoodsExcel row) {
        return Stream.of(
                row.getGoodsName(), row.getCommonName(), row.getGoodsIdCode(), row.getQuasiNumber(),
                row.getProductType(), row.getManufacturer(), row.getFormulationSpec(), row.getPackingSpec(),
                row.getDeviceCategory(), row.getProductTechnicalRequirementNumber(),
                row.getProductionLicenseOrFilingNumber(), row.getRegistrantOrFiler(), row.getStructureComposition(),
                row.getApplicableScope(), row.getOriginType(), row.getProductionLicenseNumber(),
                row.getProductCategory(), row.getProductExecutionStandard(), row.getSpecialFoodFlag(),
                row.getSpecialFood(), row.getGoodsYards(), row.getMaintenanceInterval(), row.getLowPurchaseQuantity(),
                row.getLowUnitName(), row.getIsPrescriptionDrug(), row.getGoodsSpec(), row.getWarningInventory(),
                row.getPackingUnitName(), row.getInstructionsUse(), row.getSideEffect(), row.getSimplifiedCode(),
                row.getDosageForm(), row.getUsageClassification(), row.getProductBrand(),
                row.getProductBrandAndPrecautions(), row.getPlaceOfOrigin(), row.getIngredient(), row.getTrait(),
                row.getIndication(), row.getTaboo(), row.getPrecautions(), row.getInteraction(),
                row.getAdverseReaction(), row.getStorage(), row.getEffectivePeriod(), row.getOtherDescription())
                .allMatch(StringUtils::isBlank);
    }
    private TErpGoods buildFixedGoods(
            TErpFixedGoodsExcel row,
            String typeId,
            TCrmSupplier supplier,
            SysUser user) {
        TErpGoods goods = new TErpGoods();
        goods.setSupplierClinicId(supplier.getId());
        goods.setGoodsSource(1);
        goods.setCreateId(user.getUserId().toString());
        goods.setTypeId(typeId);
        goods.setGoodsName(row.getGoodsName().trim());
        goods.setGoodsIdCode(row.getGoodsIdCode().trim());
        goods.setManufacturer(row.getManufacturer().trim());
        goods.setQuasiNumber(trim(row.getQuasiNumber()));
        goods.setFormulationSpec(trim(row.getFormulationSpec()));
        goods.setPackingSpec(trim(row.getPackingSpec()));
        goods.setCommonName(trim(row.getCommonName()));
        goods.setProductType(trim(row.getProductType()));
        goods.setDeviceCategory(trim(row.getDeviceCategory()));
        goods.setProductTechnicalRequirementNumber(trim(row.getProductTechnicalRequirementNumber()));
        goods.setProductionLicenseOrFilingNumber(trim(row.getProductionLicenseOrFilingNumber()));
        goods.setRegistrantOrFiler(trim(row.getRegistrantOrFiler()));
        goods.setStructureComposition(trim(row.getStructureComposition()));
        goods.setApplicableScope(trim(row.getApplicableScope()));
        goods.setOriginType(trim(row.getOriginType()));
        goods.setProductionLicenseNumber(trim(row.getProductionLicenseNumber()));
        goods.setProductCategory(trim(row.getProductCategory()));
        goods.setProductExecutionStandard(trim(row.getProductExecutionStandard()));
        goods.setSpecialFoodFlag(parseFlag(row.getSpecialFoodFlag(), "是否特殊食品"));
        goods.setSpecialFood(trim(row.getSpecialFood()));
        goods.setGoodsYards(trim(row.getGoodsYards()));
        parseInteger(row.getMaintenanceInterval(), "保养周期");
        goods.setMaintenanceInterval(trim(row.getMaintenanceInterval()));
        goods.setLowPurchaseQuantity(parseInteger(row.getLowPurchaseQuantity(), "最低采购数量"));
        goods.setIsPrescriptionDrug(parseFlag(row.getIsPrescriptionDrug(), "是否为处方药"));
        goods.setGoodsSpec(StringUtils.isBlank(row.getGoodsSpec())
                ? buildGoodsSpec(row.getFormulationSpec(), row.getPackingSpec())
                : row.getGoodsSpec().trim());
        goods.setWarningInventory(parseInteger(row.getWarningInventory(), "预警库存"));
        goods.setLowUnitId(findUnitId(row.getLowUnitName(), "最低采购单位"));
        goods.setPackingUnitId(findUnitId(row.getPackingUnitName(), "包装单位"));
        goods.setInstructionsUse(trim(row.getInstructionsUse()));
        goods.setSideEffect(StringUtils.isBlank(row.getSideEffect())
                ? trim(row.getAdverseReaction()) : row.getSideEffect().trim());
        goods.setSimplifiedCode(trim(row.getSimplifiedCode()));
        goods.setDosageForm(trim(row.getDosageForm()));
        goods.setUsageClassification(trim(row.getUsageClassification()));
        goods.setProductBrand(StringUtils.isBlank(row.getProductBrand())
                ? trim(row.getProductBrandAndPrecautions())
                : row.getProductBrand().trim());
        goods.setPlaceOfOrigin(trim(row.getPlaceOfOrigin()));
        goods.setIngredient(trim(row.getIngredient()));
        goods.setTrait(trim(row.getTrait()));
        goods.setIndication(trim(row.getIndication()));
        goods.setTaboo(trim(row.getTaboo()));
        goods.setPrecautions(trim(row.getPrecautions()));
        goods.setInteraction(trim(row.getInteraction()));
        goods.setStorage(trim(row.getStorage()));
        goods.setEffectivePeriod(trim(row.getEffectivePeriod()));
        goods.setOtherDescription(trim(row.getOtherDescription()));
        return goods;
    }
    private String findUnitId(String unitName, String fieldName) {
        if (StringUtils.isBlank(unitName)) {
            return null;
        }
        TErpGoodsUnit unit = erpGoodsUnitService.getOne(
                new LambdaQueryWrapper<TErpGoodsUnit>().eq(TErpGoodsUnit::getUnitName, unitName.trim()));
        if (unit == null) {
            throw new IllegalArgumentException(fieldName + "有误");
        }
        return unit.getId();
    }
    private Integer parseInteger(String value, String fieldName) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        try {
            return new BigDecimal(value.trim()).intValueExact();
        } catch (Exception e) {
            throw new IllegalArgumentException(fieldName + "有误");
        }
    }
    private Integer parseFlag(String value, String fieldName) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        String normalized = value.trim();
        if ("是".equals(normalized) || "1".equals(normalized)) {
            return 1;
        }
        if ("否".equals(normalized) || "2".equals(normalized)) {
            return 2;
        }
        throw new IllegalArgumentException(fieldName + "有误");
    }
    private String trim(String value) {
        return StringUtils.isBlank(value) ? null : value.trim();
    }
    @ApiOperation(value = "平台设置分佣")
    @PostMapping(value = "/setCommission")
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TErpGoodsTypeController.java
@@ -6,6 +6,7 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.constant.GoodsTypeConstants;
import com.ruoyi.system.model.TErpGoods;
import com.ruoyi.system.model.TErpGoodsType;
import com.ruoyi.system.query.TErpGoodsTypeQuery;
@@ -56,6 +57,7 @@
    @ApiOperation(value = "获取erp商品类型列表")
    @PostMapping(value = "/list")
    public R<List<TErpGoodsType>> list() {
        erpGoodsTypeService.ensureBuiltinTypes();
        return R.ok(erpGoodsTypeService.list(Wrappers.lambdaQuery(TErpGoodsType.class).orderByDesc(TErpGoodsType::getCreateTime)));
    }
@@ -69,6 +71,7 @@
        if (erpGoodsTypeService.isExit(dto)) {
            return R.fail("erp商品类型名称已存在");
        }
        dto.setId(null);
        return R.ok(erpGoodsTypeService.save(dto));
    }
@@ -82,7 +85,12 @@
        if (erpGoodsTypeService.isExit(dto)) {
            return R.fail("erp商品类型名称已存在");
        }
        return R.ok(erpGoodsTypeService.updateById(dto));
        TErpGoodsType existing = erpGoodsTypeService.getById(dto.getId());
        if (existing == null) {
            return R.fail("商品类型不存在");
        }
        existing.setTypeName(dto.getTypeName());
        return R.ok(erpGoodsTypeService.updateById(existing));
    }
    /**
@@ -101,6 +109,9 @@
    @ApiOperation(value = "删除erp商品类型")
    @DeleteMapping(value = "/deleteById")
    public R<Boolean> deleteById(@RequestParam String id) {
        if (GoodsTypeConstants.isBuiltin(id)) {
            return R.fail("内置商品类型不可删除");
        }
        long count = erpGoodsService.count(Wrappers.lambdaQuery(TErpGoods.class).eq(TErpGoods::getTypeId, id));
        if (count>0) {
            return R.fail("该商品分类下有商品,无法删除");
@@ -115,6 +126,9 @@
    @ApiOperation(value = "批量删除erp商品类型")
    @DeleteMapping(value = "/deleteByIds")
    public R<Boolean> deleteByIds(@RequestBody List<String> ids) {
        if (ids.stream().anyMatch(GoodsTypeConstants::isBuiltin)) {
            return R.fail("内置商品类型不可删除");
        }
        List<TErpGoods> items = erpGoodsService.list(Wrappers.lambdaQuery(TErpGoods.class).in(TErpGoods::getTypeId, ids));
        for (String id : ids) {
            if (items.stream().anyMatch(t -> t.getTypeId().equals(id))) {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TOrderController.java
@@ -142,25 +142,50 @@
            String apiPath = "api/v3/labs/trans/preorder";
            String body = "{\n" +
                    "\t\"req_time\": \"" + time + "\",\n" +
                    "\t\"version\": \"3.0\",\n" +
                    "\t\"req_data\": {\n" +
                    "\t\t\"out_trade_no\": \"" + outTradeNo + "\",\n" +
                    "\t\t\"merchant_no\": \""+supplier.getRecvMerchantNo()+"\",\n" +
                    "\t\t\"term_no\": \""+supplier.getTermNo()+"\",\n" +
            String body="";
            if("822641048160MMJ".equals(supplier.getRecvMerchantNo())){
                body= "{\n" +
                        "\t\"req_time\": \"" + time + "\",\n" +
                        "\t\"version\": \"3.0\",\n" +
                        "\t\"req_data\": {\n" +
                        "\t\t\"out_trade_no\": \"" + outTradeNo + "\",\n" +
                        "\t\t\"merchant_no\": \""+supplier.getRecvMerchantNo()+"\",\n" +
                        "\t\t\"term_no\": \""+supplier.getTermNo()+"\",\n" +
//                    "\t\t\"notify_url\": \"http://221.182.45.100:8089/t-sys-order/messageHandle\",\n" +
                    "\t\t\"notify_url\": \"https://haizhentong.com/manage/t-sys-order/messageHandle\",\n" +
                    "\t\t\"location_info\": {\n" +
                    "\t\t\t\"request_ip\": \"" + ipAddr + "\"\n" +
                    "\t\t},\n" +
                    "\t\t\"subject\": \"商品采购\",\n" +
                    "\t\t\"settle_type\": \"1\",\n" +
                    "\t\t\"total_amount\": " + amount + ",\n" +
                    "\t\t\"account_type\": \"" + type + "\",\n" +
                    "\t\t\"trans_type\": \""+trans_type+"\"\n" +
                    "\t}\n" +
                    "}";
                        "\t\t\"notify_url\": \"https://haizhentong.com/manage/t-sys-order/messageHandle\",\n" +
                        "\t\t\"location_info\": {\n" +
                        "\t\t\t\"request_ip\": \"" + ipAddr + "\"\n" +
                        "\t\t},\n" +
                        "\t\t\"subject\": \"商品采购\",\n" +
                        "\t\t\"settle_type\": \"0\",\n" +
                        "\t\t\"total_amount\": " + amount + ",\n" +
                        "\t\t\"account_type\": \"" + type + "\",\n" +
                        "\t\t\"trans_type\": \""+trans_type+"\"\n" +
                        "\t}\n" +
                        "}";
            }else {
                 body = "{\n" +
                        "\t\"req_time\": \"" + time + "\",\n" +
                        "\t\"version\": \"3.0\",\n" +
                        "\t\"req_data\": {\n" +
                        "\t\t\"out_trade_no\": \"" + outTradeNo + "\",\n" +
                        "\t\t\"merchant_no\": \""+supplier.getRecvMerchantNo()+"\",\n" +
                        "\t\t\"term_no\": \""+supplier.getTermNo()+"\",\n" +
//                    "\t\t\"notify_url\": \"http://221.182.45.100:8089/t-sys-order/messageHandle\",\n" +
                        "\t\t\"notify_url\": \"https://haizhentong.com/manage/t-sys-order/messageHandle\",\n" +
                        "\t\t\"location_info\": {\n" +
                        "\t\t\t\"request_ip\": \"" + ipAddr + "\"\n" +
                        "\t\t},\n" +
                        "\t\t\"subject\": \"商品采购\",\n" +
                        "\t\t\"settle_type\": \"1\",\n" +
                        "\t\t\"total_amount\": " + amount + ",\n" +
                        "\t\t\"account_type\": \"" + type + "\",\n" +
                        "\t\t\"trans_type\": \""+trans_type+"\"\n" +
                        "\t}\n" +
                        "}";
            }
            String authorization = getAuthorization(body);
            System.err.println("支付请求"+body);
@@ -295,33 +320,42 @@
            erpProcurement.setMoney(payMoney);
            erpProcurement.setOutSeparateNo(outTradeNo);
            erpProcurementService.updateById(erpProcurement);
            // 添加平台的
            map.put("recv_datas", objects);
            reqMap.put("req_data", map);
            TCrmSupplier supplier = crmSupplierService.getById(erpProcurement.getSupplierId());
            if("822641048160MMJ".equals(supplier.getRecvMerchantNo())){
                list.forEach(e->e.setStatus(1));
                erpProcurementGoodsService.updateBatchById(list);
            }else {
                // 添加平台的
                map.put("recv_datas", objects);
                reqMap.put("req_data", map);
            String jsonString = JSON.toJSONString(reqMap);
                String jsonString = JSON.toJSONString(reqMap);
//            String authorization1 = getAuthorization(jsonString);
//            post.header("Authorization", SCHEMA + " " + authorization);
//
//            post.body(jsonString);
//            HttpResponse response = post.execute();
//            String responseStr = response.body();
            String authorization1 = getAuthorization(jsonString);
                String authorization1 = getAuthorization(jsonString);
            Map<String, ? extends Object> maps =
                    new ImmutableMap.Builder<String, String>().
                            put("id", erpProcurement.getId()).put("authorization", authorization1).put("body", jsonString)
                            .build();
            QuartzManager.addJob(
                    RevenueSharingJob.class,
                    (RevenueSharingJob.name+erpProcurement.getId()).toUpperCase(),
                    TimeJobType.REVENUE_SHARING,
                    DateUtils.localDateTimeToDate(LocalDateTime.now().plusSeconds(30)),
                    maps
            );
                Map<String, ? extends Object> maps =
                        new ImmutableMap.Builder<String, String>().
                                put("id", erpProcurement.getId()).put("authorization", authorization1).put("body", jsonString)
                                .build();
                QuartzManager.addJob(
                        RevenueSharingJob.class,
                        (RevenueSharingJob.name+erpProcurement.getId()).toUpperCase(),
                        TimeJobType.REVENUE_SHARING,
                        DateUtils.localDateTimeToDate(LocalDateTime.now().plusSeconds(30)),
                        maps
                );
            }
//            org.apache.http.HttpResponse response1 = post(LakalaConfig.getServerUrl() + "api/v3/sacs/separate", jsonString, authorization1);
//            String responseStr = IOUtils.toString(response1.getEntity().getContent(), ENCODING);
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/PdfUtils.java
@@ -403,4 +403,6 @@
        return result;
    }
}
ruoyi-admin/src/main/resources/application-test.yml
@@ -98,7 +98,7 @@
#        url: jdbc:mysql://124.71.107.223:10633/haizhentong?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
#        username: haizhentong
#        password: hzt@2025Mysql!
        url: jdbc:mysql://192.168.110.34:3306/haizhentong?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        url: jdbc:mysql://192.168.110.111:3306/haizhentong?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: 123456
      # 从库数据源
ruoyi-admin/src/main/resources/application.yml
@@ -1,4 +1,4 @@
# 项目相关配置
spring:
  profiles:
    active: prod
    active: test
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TSysAppUserController.java
@@ -123,7 +123,7 @@
        sysAppUserVO.setSysOrders(sysOrders);
        // 查询诊所账号
        SysUser sysUser = sysUserService.selectByPhone(sysAppUserVO.getPhone());
        if(Objects.nonNull(sysUser)){
        if(Objects.nonNull(sysUser) && sysUser.getRoleType() == 5){
            sysAppUserVO.setIsClinicUser(1);
            // 查询诊所信息
            TCrmClinic crmClinic = crmClinicService.getOne(Wrappers.lambdaQuery(TCrmClinic.class)
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/WxPayController.java
@@ -14,11 +14,11 @@
import com.ruoyi.system.service.TSysInspectionService;
import com.ruoyi.system.service.TSysOtherConfigService;
import com.ruoyi.system.service.TSysPayRecordService;
import com.ruoyi.system.utils.xiMaUtil.AesUtil;
import com.ruoyi.system.wxPay.enums.TradeStateEnum;
import com.ruoyi.system.wxPay.model.WeixinPayProperties;
import com.ruoyi.system.wxPay.utils.WxTimeUtils;
import com.ruoyi.system.wxPay.utils.WxV3Pay;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -122,11 +122,11 @@
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(weixinPayProperties.getV3().getApiKey().getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = JSONObject.parse(decryptedData);
//            AesUtil aesUtil = new AesUtil(weixinPayProperties.getV3().getApiKey().getBytes(StandardCharsets.UTF_8));
//            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
//                    resource.getString("ciphertext"));
//            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = JSONObject.parse("");
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
ruoyi-applet/src/main/resources/application.yml
@@ -1,4 +1,4 @@
# 项目相关配置
spring:
  profiles:
    active: prod
    active: test
ruoyi-system/src/main/java/com/ruoyi/system/constant/GoodsTypeConstants.java
New file
@@ -0,0 +1,37 @@
package com.ruoyi.system.constant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
 * 内置商品类型。名称允许修改,业务判断必须使用固定 ID。
 */
public final class GoodsTypeConstants {
    public static final String DRUG_ID = "00000000000000000000000000000001";
    public static final String MEDICAL_DEVICE_ID = "00000000000000000000000000000002";
    public static final String FOOD_ID = "00000000000000000000000000000003";
    public static final Map<String, String> BUILTIN_TYPES;
    static {
        Map<String, String> types = new LinkedHashMap<>();
        types.put(DRUG_ID, "药品类");
        types.put(MEDICAL_DEVICE_ID, "医疗器械类");
        types.put(FOOD_ID, "食品类");
        BUILTIN_TYPES = Collections.unmodifiableMap(types);
    }
    private GoodsTypeConstants() {
    }
    public static boolean isBuiltin(String id) {
        return id != null && BUILTIN_TYPES.containsKey(id);
    }
    public static Set<String> ids() {
        return BUILTIN_TYPES.keySet();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/GoodsSortUpdateDto.java
New file
@@ -0,0 +1,10 @@
package com.ruoyi.system.dto;
import lombok.Data;
@Data
public class GoodsSortUpdateDto {
    private String id;
    private Integer sort;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpClinicGoodsAddDto.java
@@ -21,7 +21,6 @@
    @ApiModelProperty(value = "供应商名称")
    @NotBlank ( message = "供应商名称不能为空")
    private String supplierName;
@@ -34,11 +33,9 @@
    private String manufacturer;
    @ApiModelProperty(value = "制剂规格")
    @NotBlank ( message = "制剂规格不能为空")
    private String formulationSpec;
    @ApiModelProperty(value = "包装规格")
    @NotBlank ( message = "包装规格不能为空")
    private String packingSpec;
@@ -49,6 +46,9 @@
    @ApiModelProperty(value = "商品条码")
    @NotBlank ( message = "商品条码不能为空")
    private String goodsIdCode;
    @ApiModelProperty(value = "商品标识码")
    private String goodsYards;
    @ApiModelProperty(value = "保养周期")
@@ -62,9 +62,14 @@
    @ApiModelProperty(value = "是否为处方药 1=是 2=否")
    private Integer isPrescriptionDrug;
    @ApiModelProperty(value = "最低采购数量")
    private Integer lowPurchaseQuantity;
    @ApiModelProperty(value = "最低采购单位,单位id")
    private String lowUnitId;
    @ApiModelProperty(value = "商品规格")
    @NotBlank ( message = "商品规格不能为空")
    private String goodsSpec;
@@ -91,6 +96,12 @@
    @ApiModelProperty(value = "副作用")
    private String sideEffect;
    @ApiModelProperty(value = "封面图")
    private String cover;
    @ApiModelProperty(value = "详情图")
    private String detailPictures;
@@ -127,6 +138,26 @@
    @ApiModelProperty(value = "相互作用")
    private String interaction;
    @ApiModelProperty(value = "贮藏")
    private String storage;
    private String commonName;
    private String productType;
    private String deviceCategory;
    private String productTechnicalRequirementNumber;
    private String productionLicenseOrFilingNumber;
    private String registrantOrFiler;
    private String structureComposition;
    private String applicableScope;
    private String originType;
    private String productionLicenseNumber;
    private String productCategory;
    private String productExecutionStandard;
    private Integer specialFoodFlag;
    private String specialFood;
    private String effectivePeriod;
    private String otherDescription;
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpClinicGoodsUpdateDto.java
@@ -22,7 +22,6 @@
    @ApiModelProperty(value = "供应商名称")
    @NotBlank ( message = "供应商名称不能为空")
    private String supplierName;
@@ -35,11 +34,9 @@
    private String manufacturer;
    @ApiModelProperty(value = "制剂规格")
    @NotBlank ( message = "制剂规格不能为空")
    private String formulationSpec;
    @ApiModelProperty(value = "包装规格")
    @NotBlank ( message = "包装规格不能为空")
    private String packingSpec;
@@ -50,6 +47,9 @@
    @ApiModelProperty(value = "商品条码")
    @NotBlank ( message = "商品条码不能为空")
    private String goodsIdCode;
    @ApiModelProperty(value = "商品标识码")
    private String goodsYards;
    @ApiModelProperty(value = "保养周期")
@@ -63,9 +63,14 @@
    @ApiModelProperty(value = "是否为处方药 1=是 2=否")
    private Integer isPrescriptionDrug;
    @ApiModelProperty(value = "最低采购数量")
    private Integer lowPurchaseQuantity;
    @ApiModelProperty(value = "最低采购单位,单位id")
    private String lowUnitId;
    @ApiModelProperty(value = "商品规格")
    @NotBlank ( message = "商品规格不能为空")
    private String goodsSpec;
@@ -92,6 +97,12 @@
    @ApiModelProperty(value = "副作用")
    private String sideEffect;
    @ApiModelProperty(value = "封面图")
    private String cover;
    @ApiModelProperty(value = "详情图")
    private String detailPictures;
@@ -128,6 +139,26 @@
    @ApiModelProperty(value = "相互作用")
    private String interaction;
    @ApiModelProperty(value = "贮藏")
    private String storage;
    private String commonName;
    private String productType;
    private String deviceCategory;
    private String productTechnicalRequirementNumber;
    private String productionLicenseOrFilingNumber;
    private String registrantOrFiler;
    private String structureComposition;
    private String applicableScope;
    private String originType;
    private String productionLicenseNumber;
    private String productCategory;
    private String productExecutionStandard;
    private Integer specialFoodFlag;
    private String specialFood;
    private String effectivePeriod;
    private String otherDescription;
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpGoodsAddDto.java
@@ -32,11 +32,101 @@
    @ApiModelProperty(value = "制剂规格")
    @NotBlank(message = "制剂规格不能为空")
    private String formulationSpec;
    @ApiModelProperty(value = "包装规格")
    @NotBlank(message = "包装规格不能为空")
    private String packingSpec;
    @ApiModelProperty(value = "封面图")
    private String cover;
    @ApiModelProperty(value = "详情图")
    private String detailPictures;
    @ApiModelProperty(value = "商品标识码")
    private String goodsYards;
    @ApiModelProperty(value = "保养周期")
    private String maintenanceInterval;
    @ApiModelProperty(value = "保养周期单位")
    private String maintenanceIntervalUnit;
    @ApiModelProperty(value = "最低采购数量")
    private Integer lowPurchaseQuantity;
    @ApiModelProperty(value = "最低采购单位")
    private String lowUnitId;
    @ApiModelProperty(value = "是否为处方药 1=是 2=否")
    private Integer isPrescriptionDrug;
    @ApiModelProperty(value = "商品规格")
    private String goodsSpec;
    @ApiModelProperty(value = "预警库存")
    private Integer warningInventory;
    @ApiModelProperty(value = "包装单位")
    private String packingUnitId;
    @ApiModelProperty(value = "使用说明")
    private String instructionsUse;
    @ApiModelProperty(value = "副作用")
    private String sideEffect;
    @ApiModelProperty(value = "拼音简码")
    private String simplifiedCode;
    @ApiModelProperty(value = "剂型")
    private String dosageForm;
    @ApiModelProperty(value = "用途分类")
    private String usageClassification;
    @ApiModelProperty(value = "商品品牌名")
    private String productBrand;
    @ApiModelProperty(value = "产地")
    private String placeOfOrigin;
    @ApiModelProperty(value = "成分")
    private String ingredient;
    @ApiModelProperty(value = "性状")
    private String trait;
    @ApiModelProperty(value = "适应症")
    private String indication;
    @ApiModelProperty(value = "禁忌")
    private String taboo;
    @ApiModelProperty(value = "注意事项")
    private String precautions;
    @ApiModelProperty(value = "相互作用")
    private String interaction;
    @ApiModelProperty(value = "贮藏")
    private String storage;
    private String commonName;
    private String productType;
    private String deviceCategory;
    private String productTechnicalRequirementNumber;
    private String productionLicenseOrFilingNumber;
    private String registrantOrFiler;
    private String structureComposition;
    private String applicableScope;
    private String originType;
    private String productionLicenseNumber;
    private String productCategory;
    private String productExecutionStandard;
    private Integer specialFoodFlag;
    private String specialFood;
    private String effectivePeriod;
    private String otherDescription;
}
ruoyi-system/src/main/java/com/ruoyi/system/dto/TErpGoodsInfoSaveDto.java
@@ -119,4 +119,21 @@
    @ApiModelProperty(value = "生产厂家")
    private String manufacturer;
    private String commonName;
    private String productType;
    private String deviceCategory;
    private String productTechnicalRequirementNumber;
    private String productionLicenseOrFilingNumber;
    private String registrantOrFiler;
    private String structureComposition;
    private String applicableScope;
    private String originType;
    private String productionLicenseNumber;
    private String productCategory;
    private String productExecutionStandard;
    private Integer specialFoodFlag;
    private String specialFood;
    private String effectivePeriod;
    private String otherDescription;
}
ruoyi-system/src/main/java/com/ruoyi/system/export/TErpGoodsDetailExport.java
New file
@@ -0,0 +1,172 @@
package com.ruoyi.system.export;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
 * 商品详情导出。
 */
@Data
public class TErpGoodsDetailExport implements Serializable {
    private static final long serialVersionUID = 1L;
    @Excel(name = "所属供应商", width = 20)
    private String supplierName;
    @Excel(name = "商品名称", width = 25)
    private String goodsName;
    @Excel(name = "商品分类", width = 15)
    private String typeName;
    @Excel(name = "商品条形码", width = 20)
    private String goodsIdCode;
    @Excel(name = "通用名", width = 20)
    private String commonName;
    @Excel(name = "批准文号或国药准字号", width = 25)
    private String quasiNumber;
    @Excel(name = "商品细分类型", width = 20)
    private String productType;
    @Excel(name = "器械类别", width = 20)
    private String deviceCategory;
    @Excel(name = "产品技术要求编号", width = 25)
    private String productTechnicalRequirementNumber;
    @Excel(name = "生产许可证或备案凭证编号", width = 30)
    private String productionLicenseOrFilingNumber;
    @Excel(name = "注册人或备案人", width = 25)
    private String registrantOrFiler;
    @Excel(name = "结构及组成", width = 35)
    private String structureComposition;
    @Excel(name = "适用范围", width = 35)
    private String applicableScope;
    @Excel(name = "国产/进口", width = 15)
    private String originType;
    @Excel(name = "生产许可证号", width = 25)
    private String productionLicenseNumber;
    @Excel(name = "产品类别", width = 20)
    private String productCategory;
    @Excel(name = "商品执行标准", width = 25)
    private String productExecutionStandard;
    @Excel(name = "是否特殊食品", width = 15, replace = {"是_1", "否_2"})
    private Integer specialFoodFlag;
    @Excel(name = "特殊食品", width = 20)
    private String specialFood;
    @Excel(name = "生产厂家", width = 25)
    private String manufacturer;
    @Excel(name = "制剂规格", width = 20)
    private String formulationSpec;
    @Excel(name = "包装规格", width = 20)
    private String packingSpec;
    @Excel(name = "商品标识码", width = 20)
    private String goodsYards;
    @Excel(name = "保养周期", width = 15)
    private String maintenanceInterval;
    @Excel(name = "保养周期单位", width = 15)
    private String maintenanceIntervalUnit;
    @Excel(name = "诊所采购价格", width = 15)
    private BigDecimal clinicPurchasePrice;
    @Excel(name = "平台分佣金额", width = 15)
    private BigDecimal platformCommissionPrice;
    @Excel(name = "是否为处方药", width = 15, replace = {"是_1", "否_2", "_3"})
    private Integer isPrescriptionDrug;
    @Excel(name = "最低采购数量", width = 15)
    private Integer lowPurchaseQuantity;
    @Excel(name = "最低采购单位", width = 15)
    private String lowUnitName;
    @Excel(name = "商品规格", width = 20)
    private String goodsSpec;
    @Excel(name = "预警库存", width = 15)
    private Integer warningInventory;
    @Excel(name = "包装单位", width = 15)
    private String packingUnitName;
    @Excel(name = "商品售价", width = 15)
    private BigDecimal salesAmount;
    @Excel(name = "使用说明", width = 35)
    private String instructionsUse;
    @Excel(name = "副作用", width = 35)
    private String sideEffect;
    @Excel(name = "封面图", width = 40)
    private String cover;
    @Excel(name = "详情图", width = 40)
    private String detailPictures;
    @Excel(name = "拼音简码", width = 15)
    private String simplifiedCode;
    @Excel(name = "剂型", width = 15)
    private String dosageForm;
    @Excel(name = "用途分类", width = 20)
    private String usageClassification;
    @Excel(name = "商品名(品牌)", width = 20)
    private String productBrand;
    @Excel(name = "产地", width = 20)
    private String placeOfOrigin;
    @Excel(name = "主要成分", width = 35)
    private String ingredient;
    @Excel(name = "性状", width = 35)
    private String trait;
    @Excel(name = "适应症", width = 35)
    private String indication;
    @Excel(name = "禁忌", width = 35)
    private String taboo;
    @Excel(name = "注意事项", width = 35)
    private String precautions;
    @Excel(name = "相互作用", width = 35)
    private String interaction;
    @Excel(name = "贮藏", width = 25)
    private String storage;
    @Excel(name = "有效时间", width = 20)
    private String effectivePeriod;
    @Excel(name = "其他说明", width = 35)
    private String otherDescription;
}
ruoyi-system/src/main/java/com/ruoyi/system/importExcel/TErpFixedGoodsExcel.java
New file
@@ -0,0 +1,150 @@
package com.ruoyi.system.importExcel;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
/**
 * 供应商固定商品类型导入模板。
 * 三个工作表共用一套字段模型,按所选商品类型读取对应工作表。
 */
@Data
public class TErpFixedGoodsExcel {
    @Excel(name = "商品名称")
    private String goodsName;
    @Excel(name = "通用名")
    private String commonName;
    @Excel(name = "商品条形码")
    private String goodsIdCode;
    @Excel(name = "批准文号或国药准字")
    private String quasiNumber;
    @Excel(name = "商品类型")
    private String productType;
    @Excel(name = "生产厂家")
    private String manufacturer;
    @Excel(name = "剂型规格")
    private String formulationSpec;
    @Excel(name = "包装规格")
    private String packingSpec;
    @Excel(name = "器械类别")
    private String deviceCategory;
    @Excel(name = "产品技术要求编号")
    private String productTechnicalRequirementNumber;
    @Excel(name = "生产许可证或备案凭证编号")
    private String productionLicenseOrFilingNumber;
    @Excel(name = "注册人或备案人")
    private String registrantOrFiler;
    @Excel(name = "结构及组成")
    private String structureComposition;
    @Excel(name = "适用范围")
    private String applicableScope;
    @Excel(name = "国产/进口")
    private String originType;
    @Excel(name = "生产许可证号")
    private String productionLicenseNumber;
    @Excel(name = "产品类别")
    private String productCategory;
    @Excel(name = "商品执行标准")
    private String productExecutionStandard;
    @Excel(name = "是否特殊食品")
    private String specialFoodFlag;
    @Excel(name = "特殊食品")
    private String specialFood;
    @Excel(name = "商品标识码")
    private String goodsYards;
    @Excel(name = "保养周期")
    private String maintenanceInterval;
    @Excel(name = "最低采购数量")
    private String lowPurchaseQuantity;
    @Excel(name = "最低采购单位")
    private String lowUnitName;
    @Excel(name = "是否为处方药")
    private String isPrescriptionDrug;
    @Excel(name = "商品规格")
    private String goodsSpec;
    @Excel(name = "预警库存")
    private String warningInventory;
    @Excel(name = "包装单位")
    private String packingUnitName;
    @Excel(name = "使用说明")
    private String instructionsUse;
    @Excel(name = "副作用")
    private String sideEffect;
    @Excel(name = "拼音简码")
    private String simplifiedCode;
    @Excel(name = "剂型")
    private String dosageForm;
    @Excel(name = "用途分类")
    private String usageClassification;
    @Excel(name = "商品名(品牌)")
    private String productBrand;
    @Excel(name = "商品名(品牌),注意事项")
    private String productBrandAndPrecautions;
    @Excel(name = "产地")
    private String placeOfOrigin;
    @Excel(name = "主要成份")
    private String ingredient;
    @Excel(name = "性状")
    private String trait;
    @Excel(name = "适应症")
    private String indication;
    @Excel(name = "禁忌")
    private String taboo;
    @Excel(name = "注意事项")
    private String precautions;
    @Excel(name = "相互作用")
    private String interaction;
    @Excel(name = "不良反应")
    private String adverseReaction;
    @Excel(name = "贮藏")
    private String storage;
    @Excel(name = "有效时间")
    private String effectivePeriod;
    @Excel(name = "其他说明")
    private String otherDescription;
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TErpGoods.java
@@ -79,6 +79,70 @@
    @Excel(name = "*包装规格")
    private String packingSpec;
    @ApiModelProperty(value = "通用名")
    @TableField("common_name")
    private String commonName;
    @ApiModelProperty(value = "商品细分类型")
    @TableField("product_type")
    private String productType;
    @ApiModelProperty(value = "器械类别")
    @TableField("device_category")
    private String deviceCategory;
    @ApiModelProperty(value = "产品技术要求编号")
    @TableField("product_technical_requirement_number")
    private String productTechnicalRequirementNumber;
    @ApiModelProperty(value = "生产许可证或备案凭证编号")
    @TableField("production_license_or_filing_number")
    private String productionLicenseOrFilingNumber;
    @ApiModelProperty(value = "注册人或备案人")
    @TableField("registrant_or_filer")
    private String registrantOrFiler;
    @ApiModelProperty(value = "结构及组成")
    @TableField("structure_composition")
    private String structureComposition;
    @ApiModelProperty(value = "适用范围")
    @TableField("applicable_scope")
    private String applicableScope;
    @ApiModelProperty(value = "国产/进口")
    @TableField("origin_type")
    private String originType;
    @ApiModelProperty(value = "生产许可证号")
    @TableField("production_license_number")
    private String productionLicenseNumber;
    @ApiModelProperty(value = "产品类别")
    @TableField("product_category")
    private String productCategory;
    @ApiModelProperty(value = "商品执行标准")
    @TableField("product_execution_standard")
    private String productExecutionStandard;
    @ApiModelProperty(value = "是否特殊食品")
    @TableField("special_food_flag")
    private Integer specialFoodFlag;
    @ApiModelProperty(value = "特殊食品")
    @TableField("special_food")
    private String specialFood;
    @ApiModelProperty(value = "有效时间")
    @TableField("effective_period")
    private String effectivePeriod;
    @ApiModelProperty(value = "其他说明")
    @TableField("other_description")
    private String otherDescription;
    @ApiModelProperty(value = "商品类型名称")
    @TableField(exist = false)
    @Excel(name = "*商品类型")
@@ -288,4 +352,9 @@
    @ApiModelProperty(value = "记录创建人id,前端忽略")
    @TableField(value = "create_id", fill = FieldFill.INSERT)
    private String createId;
    /**
     * 排序
     */
    private Integer sort;
}
ruoyi-system/src/main/java/com/ruoyi/system/query/TErpGoodsAdminQuery.java
@@ -22,6 +22,12 @@
    @ApiModelProperty(value = "国药准字号")
    private String quasiNumber;
    @ApiModelProperty(value = "关键字号")
    private String keyword;
    @ApiModelProperty(value = "是否处方药 1是 2否")
    private Integer isPrescriptionDrug;
    @ApiModelProperty(value = "1启用 2禁用")
    private Integer state;
ruoyi-system/src/main/java/com/ruoyi/system/query/TErpGoodsQuery.java
@@ -25,6 +25,12 @@
    @ApiModelProperty(value = "国药准字号")
    private String quasiNumber;
    @ApiModelProperty(value = "关键字号")
    private String keyword;
    @ApiModelProperty(value = "是否处方药 1是 2否")
    private Integer isPrescriptionDrug;
    @ApiModelProperty(value = "1启用 2禁用")
    private Integer state;
ruoyi-system/src/main/java/com/ruoyi/system/service/TErpGoodsTypeService.java
@@ -27,4 +27,9 @@
     * @return
     */
    PageInfo<TErpGoodsType> pageList(TErpGoodsTypeQuery query);
    /**
     * 幂等补齐系统内置商品类型。
     */
    void ensureBuiltinTypes();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCrmSupplierServiceImpl.java
@@ -146,10 +146,13 @@
        crmSupplierCloudShopService.remove(Wrappers.lambdaQuery(TCrmSupplierCloudShop.class).eq(TCrmSupplierCloudShop::getSupplierId, dto.getId()));
        // 添加供应商云仓店铺信息
        List<TCrmSupplierCloudShop> crmSupplierCloudShops = dto.getCrmSupplierCloudShops();
        for (TCrmSupplierCloudShop crmSupplierCloudShop : crmSupplierCloudShops) {
            crmSupplierCloudShop.setSupplierId(dto.getId());
        if(crmSupplierCloudShops!=null){
            for (TCrmSupplierCloudShop crmSupplierCloudShop : crmSupplierCloudShops) {
                crmSupplierCloudShop.setSupplierId(dto.getId());
            }
            crmSupplierCloudShopService.saveBatch(crmSupplierCloudShops);
        }
        crmSupplierCloudShopService.saveBatch(crmSupplierCloudShops);
        if(Objects.nonNull(user)){
            // 修改账号
            user.setPhonenumber(dto.getPhone());
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpGoodsServiceImpl.java
@@ -239,9 +239,23 @@
            List<TErpGoodsType> typeList = erpGoodsTypeMapper.selectBatchIds(typeIds);
            for (TErpGoodsVO tErpGoodsVO : list) {
                typeList.stream().filter(t -> t.getId().equals(tErpGoodsVO.getTypeId())).findFirst().ifPresent(t -> tErpGoodsVO.setTypeName(t.getTypeName()));
                tErpGoodsVO.setTypeName(tErpGoodsVO.getTypeName());
            }
        }
        List<String> supplierIds = list.stream()
                .filter(goods -> Objects.equals(goods.getGoodsSource(), 1))
                .map(TErpGoods::getSupplierClinicId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (!supplierIds.isEmpty()) {
            Map<String, String> supplierNames = crmSupplierMapper.selectBatchIds(supplierIds).stream()
                    .collect(Collectors.toMap(TCrmSupplier::getId, TCrmSupplier::getSupplierName));
            list.forEach(goods -> {
                if (Objects.equals(goods.getGoodsSource(), 1)) {
                    goods.setSupplierName(supplierNames.get(goods.getSupplierClinicId()));
                }
            });
        }
        List<String> lowUnitIds = list.stream().map(TErpGoods::getLowUnitId).collect(Collectors.toList());
        if (!lowUnitIds.isEmpty()) {
            List<TErpGoodsUnit> tErpGoodsUnits = erpGoodsUnitMapper.selectBatchIds(lowUnitIds);
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpGoodsTypeServiceImpl.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.constant.GoodsTypeConstants;
import com.ruoyi.system.model.TErpGoodsType;
import com.ruoyi.system.mapper.TErpGoodsTypeMapper;
import com.ruoyi.system.query.TErpGoodsTypeQuery;
@@ -11,6 +12,7 @@
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * <p>
@@ -35,9 +37,23 @@
    @Override
    public PageInfo<TErpGoodsType> pageList(TErpGoodsTypeQuery query) {
        ensureBuiltinTypes();
        PageInfo<TErpGoodsType> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TErpGoodsType> list = this.baseMapper.pageList(query,pageInfo);
        pageInfo.setRecords(list);
        return pageInfo;
    }
    @Override
    public void ensureBuiltinTypes() {
        for (Map.Entry<String, String> entry : GoodsTypeConstants.BUILTIN_TYPES.entrySet()) {
            if (this.getById(entry.getKey()) == null) {
                TErpGoodsType type = new TErpGoodsType();
                type.setId(entry.getKey());
                type.setTypeName(entry.getValue());
                type.setDisabled(false);
                this.save(type);
            }
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TErpProcurementServiceImpl.java
@@ -499,7 +499,7 @@
            tErpProcurement.setMoney(add.subtract(bigDecimal));
            if (tErpProcurement.getMoney().doubleValue() < 0) {
                throw new RuntimeException("金额设置错误,请联系平台管理员");
                throw new RuntimeException("最低平台分账不低于"+bigDecimal+"元!");
            }
            tErpProcurement.setSupplierMoney(supplierCommissionPrice);
ruoyi-system/src/main/java/com/ruoyi/system/utils/util/ObsUploadUtil.java
@@ -32,7 +32,7 @@
            obsClient.putObject(bucketName,"file/"+fileName,content,meta);// 上传Object.
            if(fileName != null && !"".equals(fileName)){
                System.out.println(fileName);
                fileName = oss_domain_cdn+"file/"+fileName;
                fileName = oss_domain+"file/"+fileName;
            }
        }
        return fileName;
@@ -47,7 +47,7 @@
            obsClient.putObject(bucketName,"file/"+fileName,content,meta);// 上传Object.
            if(fileName != null && !"".equals(fileName)){
                System.out.println(fileName);
                fileName = oss_domain_cdn+"file/"+fileName;
                fileName = oss_domain+"file/"+fileName;
            }
        }
        return fileName;
ruoyi-system/src/main/java/com/ruoyi/system/wxPay/config/WxConfig.java
@@ -13,7 +13,7 @@
 *
 * @author lihen
 */
@Configuration
//@Configuration
public class WxConfig {
    private final WeixinPayProperties weixinPayProperties;
ruoyi-system/src/main/resources/mapper/system/TErpGoodsMapper.xml
@@ -15,6 +15,22 @@
        <result column="manufacturer" property="manufacturer"/>
        <result column="formulation_spec" property="formulationSpec"/>
        <result column="packing_spec" property="packingSpec"/>
        <result column="common_name" property="commonName"/>
        <result column="product_type" property="productType"/>
        <result column="device_category" property="deviceCategory"/>
        <result column="product_technical_requirement_number" property="productTechnicalRequirementNumber"/>
        <result column="production_license_or_filing_number" property="productionLicenseOrFilingNumber"/>
        <result column="registrant_or_filer" property="registrantOrFiler"/>
        <result column="structure_composition" property="structureComposition"/>
        <result column="applicable_scope" property="applicableScope"/>
        <result column="origin_type" property="originType"/>
        <result column="production_license_number" property="productionLicenseNumber"/>
        <result column="product_category" property="productCategory"/>
        <result column="product_execution_standard" property="productExecutionStandard"/>
        <result column="special_food_flag" property="specialFoodFlag"/>
        <result column="special_food" property="specialFood"/>
        <result column="effective_period" property="effectivePeriod"/>
        <result column="other_description" property="otherDescription"/>
        <result column="type_id" property="typeId"/>
        <result column="goods_yards" property="goodsYards"/>
        <result column="maintenance_interval" property="maintenanceInterval"/>
@@ -45,7 +61,10 @@
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, supplier_clinic_id, goods_source, goods_name, goods_id_code, quasi_number, manufacturer, formulation_spec,
        packing_spec, type_id, goods_yards, maintenance_interval,maintenance_interval_unit, low_purchase_quantity,
        packing_spec, common_name, product_type, device_category, product_technical_requirement_number,
        production_license_or_filing_number, registrant_or_filer, structure_composition, applicable_scope,
        origin_type, production_license_number, product_category, product_execution_standard, special_food_flag,
        special_food, effective_period, other_description, type_id, goods_yards, maintenance_interval,maintenance_interval_unit, low_purchase_quantity,
        low_unit_id, is_prescription_drug, goods_spec, warning_inventory, sales_amount, packing_unit_id, instructions_use,
        side_effect, clinic_purchase_price, platform_commission_price, create_time, update_time, create_by,`state`,
        update_by, disabled,simplified_code,dosage_form,usage_classification,product_brand,place_of_origin,ingredient,
@@ -56,25 +75,24 @@
    <select id="pageList" resultType="com.ruoyi.system.vo.TErpGoodsVO">
        select
       t1.id
        ,  t1.supplier_clinic_id,  t1.goods_source,  t1.goods_name,  t1.goods_id_code,  t1.quasi_number,  t1.manufacturer,  t1.formulation_spec,  t1.packing_spec,  t1.type_id,  t1.goods_yards,  t1.maintenance_interval, t1.maintenance_interval_unit,  t1.low_purchase_quantity,  t1.low_unit_id,  t1.is_prescription_drug,  t1.goods_spec,  t1.warning_inventory,  t1.sales_amount,  t1.packing_unit_id,  t1.instructions_use,  t1.side_effect,  t1.clinic_purchase_price,  t1.platform_commission_price,  t1.create_time,  t1.update_time,  t1.create_by, t1.`state`,  t1.update_by,  t1.disabled,
        t1.simplified_code,
        t1.dosage_form,
        t1.usage_classification,
        t1.product_brand,
        t1.place_of_origin,
        t1.ingredient,
        t1.trait,
        t1.indication,
        t1.taboo,
        t1.precautions,
        t1.interaction,
        t1.storage,
        t1.content,
        t1.content_state,
        t1.content_remark,
        t1.state_remark,
        t1.cover,t1.detail_pictures,t1.retail_price,t1.cloud_warehouse,t1.cloud_goods_code,t1.cloud_spec_code,t1.cloud_goods_id,t1.cloud_spec_id,
        t1.id,
        t1.supplier_clinic_id, t1.goods_source, t1.goods_name, t1.goods_id_code, t1.quasi_number,
        t1.manufacturer, t1.formulation_spec, t1.packing_spec,
        t1.common_name, t1.product_type, t1.device_category, t1.product_technical_requirement_number,
        t1.production_license_or_filing_number, t1.registrant_or_filer, t1.structure_composition,
        t1.applicable_scope, t1.origin_type, t1.production_license_number, t1.product_category,
        t1.product_execution_standard, t1.special_food_flag, t1.special_food, t1.effective_period,
        t1.other_description,
        t1.type_id, t1.goods_yards, t1.maintenance_interval, t1.maintenance_interval_unit,
        t1.low_purchase_quantity, t1.low_unit_id, t1.is_prescription_drug, t1.goods_spec,
        t1.warning_inventory, t1.sales_amount, t1.packing_unit_id, t1.instructions_use, t1.side_effect,
        t1.clinic_purchase_price, t1.platform_commission_price, t1.create_time, t1.update_time,
        t1.create_by, t1.`state`, t1.update_by, t1.disabled, t1.simplified_code, t1.dosage_form,
        t1.usage_classification, t1.product_brand, t1.place_of_origin, t1.ingredient, t1.trait,
        t1.indication, t1.taboo, t1.precautions, t1.interaction, t1.storage, t1.content,
        t1.content_state, t1.content_remark, t1.state_remark, t1.cover, t1.detail_pictures,
        t1.retail_price, t1.cloud_warehouse, t1.cloud_goods_code, t1.cloud_spec_code,
        t1.cloud_goods_id, t1.cloud_spec_id,
        case t1.goods_source
        when 1 then t2.supplier_name
        when 2 then t1.supplier_name
@@ -95,6 +113,17 @@
        </if>
        <if test="query.quasiNumber != null and query.quasiNumber != ''">
            and  t1.quasi_number like concat('%',#{query.quasiNumber},'%')
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                t1.quasi_number like concat('%',#{query.keyword},'%')
                or t1.product_technical_requirement_number like concat('%',#{query.keyword},'%')
                or t1.production_license_or_filing_number like concat('%',#{query.keyword},'%')
                or t1.production_license_number like concat('%',#{query.keyword},'%')
            )
        </if>
        <if test="query.isPrescriptionDrug != null">
            and t1.is_prescription_drug = #{query.isPrescriptionDrug}
        </if>
        <if test="query.state != null ">
            and  t1.state = #{query.state}
@@ -124,6 +153,27 @@
        </if>
        <if test="query.quasiNumber != null and query.quasiNumber != ''">
            and quasi_number like concat('%',#{query.quasiNumber},'%')
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                quasi_number like concat('%',#{query.keyword},'%')
                or product_technical_requirement_number like concat('%',#{query.keyword},'%')
                or production_license_or_filing_number like concat('%',#{query.keyword},'%')
                or production_license_number like concat('%',#{query.keyword},'%')
            )
        </if>
        <if test="query.isPrescriptionDrug != null">
            and is_prescription_drug = #{query.isPrescriptionDrug}
        </if>
        <if test="query.supplierName != null and query.supplierName != ''">
            and (
                (goods_source = 1 and exists (
                    select 1 from t_crm_supplier supplier
                    where supplier.id = t_erp_goods.supplier_clinic_id
                    and supplier.supplier_name like concat('%',#{query.supplierName},'%')
                ))
                or (goods_source = 2 and supplier_name like concat('%',#{query.supplierName},'%'))
            )
        </if>
        <if test="query.state != null ">
            and state = #{query.state}
@@ -330,25 +380,24 @@
    <select id="pageAdminList" resultType="com.ruoyi.system.vo.TErpGoodsVO">
        select
        t1.id
        , t1.supplier_clinic_id, t1.goods_source, t1.goods_name, t1.goods_id_code, t1.quasi_number,t1.manufacturer, t1.formulation_spec, t1.packing_spec, t1.type_id, t1.goods_yards,
        t1.maintenance_interval,t1.maintenance_interval_unit, t1.low_purchase_quantity, t1.low_unit_id, t1.is_prescription_drug, t1.goods_spec, t1.warning_inventory,
        t1.sales_amount, t1.packing_unit_id, t1.instructions_use, t1.side_effect, t1.clinic_purchase_price, t1.platform_commission_price, t1.create_time, t1.update_time, t1.create_by,
        t1.`state`, t1.update_by, t1.disabled,
        t1.simplified_code,
        t1.dosage_form,
        t1.usage_classification,
        t1.product_brand,
        t1.place_of_origin,
        t1.ingredient,
        t1.trait,
        t1.indication,
        t1.taboo,
        t1.precautions,
        t1.interaction,
        t1.content,t1.content_state,
        t1.storage,t1.state_remark,
        t1.cover,t1.detail_pictures,t1.retail_price,t1.cloud_warehouse,t1.cloud_goods_code,t1.cloud_spec_code,t1.cloud_goods_id,t1.cloud_spec_id,
        t1.id,
        t1.supplier_clinic_id, t1.goods_source, t1.goods_name, t1.goods_id_code, t1.quasi_number,
        t1.manufacturer, t1.formulation_spec, t1.packing_spec,
        t1.common_name, t1.product_type, t1.device_category, t1.product_technical_requirement_number,
        t1.production_license_or_filing_number, t1.registrant_or_filer, t1.structure_composition,
        t1.applicable_scope, t1.origin_type, t1.production_license_number, t1.product_category,
        t1.product_execution_standard, t1.special_food_flag, t1.special_food, t1.effective_period,
        t1.other_description,
        t1.type_id, t1.goods_yards, t1.maintenance_interval, t1.maintenance_interval_unit,
        t1.low_purchase_quantity, t1.low_unit_id, t1.is_prescription_drug, t1.goods_spec,
        t1.warning_inventory, t1.sales_amount, t1.packing_unit_id, t1.instructions_use, t1.side_effect,
        t1.clinic_purchase_price, t1.platform_commission_price, t1.create_time, t1.update_time,
        t1.create_by, t1.`state`, t1.update_by, t1.disabled, t1.simplified_code, t1.dosage_form,
        t1.usage_classification, t1.product_brand, t1.place_of_origin, t1.ingredient, t1.trait,
        t1.indication, t1.taboo, t1.precautions, t1.interaction, t1.content, t1.content_state,
        t1.storage, t1.state_remark, t1.cover, t1.detail_pictures, t1.retail_price,
        t1.cloud_warehouse, t1.cloud_goods_code, t1.cloud_spec_code, t1.cloud_goods_id,
        t1.cloud_spec_id,
        t2.supplier_name
        from t_erp_goods t1 left join t_crm_supplier t2 on t1.supplier_clinic_id = t2.id and t1.goods_source =1
        where t1.disabled = 0 and t1.goods_source =1
@@ -364,6 +413,17 @@
        <if test="query.quasiNumber != null and query.quasiNumber != ''">
            and t1.quasi_number like concat('%',#{query.quasiNumber},'%')
        </if>
        <if test="query.keyword != null and query.keyword != ''">
            and (
                t1.quasi_number like concat('%',#{query.keyword},'%')
                or t1.product_technical_requirement_number like concat('%',#{query.keyword},'%')
                or t1.production_license_or_filing_number like concat('%',#{query.keyword},'%')
                or t1.production_license_number like concat('%',#{query.keyword},'%')
            )
        </if>
        <if test="query.isPrescriptionDrug != null">
            and t1.is_prescription_drug = #{query.isPrescriptionDrug}
        </if>
        <if test="query.state != null ">
            and t1.state = #{query.state}
        </if>
@@ -373,7 +433,7 @@
        <if test="query.category != null ">
            and t1.content is not null
        </if>
        order by t1.create_time desc
        order by t1.sort desc,t1.create_time desc
    </select>
    <select id="pageAddList" resultType="com.ruoyi.system.model.TErpGoods">
        SELECT t1.id,
sql/20260821_goods_builtin_type_fields.sql
New file
@@ -0,0 +1,18 @@
-- 商品内置分类表单扩展字段。部署后端代码前执行一次。
ALTER TABLE t_erp_goods
    ADD COLUMN common_name VARCHAR(100) NULL COMMENT '通用名',
    ADD COLUMN product_type VARCHAR(100) NULL COMMENT '商品细分类型',
    ADD COLUMN device_category VARCHAR(100) NULL COMMENT '器械类别',
    ADD COLUMN product_technical_requirement_number VARCHAR(100) NULL COMMENT '产品技术要求编号',
    ADD COLUMN production_license_or_filing_number VARCHAR(100) NULL COMMENT '生产许可证或备案凭证编号',
    ADD COLUMN registrant_or_filer VARCHAR(100) NULL COMMENT '注册人或备案人',
    ADD COLUMN structure_composition TEXT NULL COMMENT '结构及组成',
    ADD COLUMN applicable_scope TEXT NULL COMMENT '适用范围',
    ADD COLUMN origin_type VARCHAR(20) NULL COMMENT '国产/进口',
    ADD COLUMN production_license_number VARCHAR(100) NULL COMMENT '生产许可证号',
    ADD COLUMN product_category VARCHAR(100) NULL COMMENT '产品类别',
    ADD COLUMN product_execution_standard VARCHAR(100) NULL COMMENT '商品执行标准',
    ADD COLUMN special_food_flag TINYINT NULL COMMENT '是否特殊食品 1是 2否',
    ADD COLUMN special_food VARCHAR(100) NULL COMMENT '特殊食品',
    ADD COLUMN effective_period VARCHAR(100) NULL COMMENT '有效时间',
    ADD COLUMN other_description TEXT NULL COMMENT '其他说明';