入库操作
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
venv/
|
||||||
|
pgdata/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
26
app/__init__.py
Normal file
26
app/__init__.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from config import Config
|
||||||
|
from app.extensions import db, ma
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config)
|
||||||
|
|
||||||
|
# 初始化插件
|
||||||
|
db.init_app(app)
|
||||||
|
ma.init_app(app)
|
||||||
|
|
||||||
|
# 【新增关键步骤】: 显式导入 models,让 SQLAlchemy 认识所有的表
|
||||||
|
# 必须放在 db.init_app 之后,create_all 或 蓝图注册 之前
|
||||||
|
from app import models
|
||||||
|
|
||||||
|
# 注册路由蓝图
|
||||||
|
from app.api.v1.stocks import stock_bp
|
||||||
|
app.register_blueprint(stock_bp, url_prefix='/api/v1')
|
||||||
|
|
||||||
|
# 【可选】如果你没有用 Flask-Migrate,可以用下面这句话自动建表(开发阶段)
|
||||||
|
# with app.app_context():
|
||||||
|
# db.create_all()
|
||||||
|
|
||||||
|
return app
|
||||||
0
app/api/errors.py
Normal file
0
app/api/errors.py
Normal file
0
app/api/v1/__init__.py
Normal file
0
app/api/v1/__init__.py
Normal file
0
app/api/v1/auth.py
Normal file
0
app/api/v1/auth.py
Normal file
0
app/api/v1/materials.py
Normal file
0
app/api/v1/materials.py
Normal file
0
app/api/v1/reports.py
Normal file
0
app/api/v1/reports.py
Normal file
34
app/api/v1/stocks.py
Normal file
34
app/api/v1/stocks.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from app.services.stock_service import create_inbound_stock
|
||||||
|
from app.schemas.stock_schema import StockBuySchema
|
||||||
|
|
||||||
|
stock_bp = Blueprint('stocks', __name__)
|
||||||
|
|
||||||
|
@stock_bp.route('/buy-inbound', methods=['POST'])
|
||||||
|
def buy_inbound():
|
||||||
|
"""
|
||||||
|
采购入库接口
|
||||||
|
POST /api/v1/buy-inbound
|
||||||
|
Body: { "material_id": 1, "qty_inbound": 100, "price_unit": 10.5 ... }
|
||||||
|
"""
|
||||||
|
# 1. 接收 JSON 数据
|
||||||
|
json_data = request.get_json()
|
||||||
|
if not json_data:
|
||||||
|
return jsonify({"message": "No input data provided"}), 400
|
||||||
|
|
||||||
|
# 2. 数据校验
|
||||||
|
schema = StockBuySchema()
|
||||||
|
try:
|
||||||
|
# 这一步只做校验,不直接生成对象,因为我们要在 Service 里手动处理逻辑
|
||||||
|
data = schema.load(json_data, partial=True)
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"message": "Validation error", "errors": e.messages}), 422
|
||||||
|
|
||||||
|
# 3. 调用业务逻辑
|
||||||
|
try:
|
||||||
|
new_stock = create_inbound_stock(data)
|
||||||
|
# 4. 返回成功结果
|
||||||
|
result = schema.dump(new_stock)
|
||||||
|
return jsonify({"message": "Inbound successful", "data": result}), 201
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"message": "Internal Server Error", "error": str(e)}), 500
|
||||||
0
app/api/v1/transactions.py
Normal file
0
app/api/v1/transactions.py
Normal file
6
app/extensions.py
Normal file
6
app/extensions.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_marshmallow import Marshmallow
|
||||||
|
|
||||||
|
# 初始化数据库和序列化工具
|
||||||
|
db = SQLAlchemy()
|
||||||
|
ma = Marshmallow()
|
||||||
2
app/models/__init__.py
Normal file
2
app/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from app.models.material import MaterialBase
|
||||||
|
from app.models.stock import StockBuy
|
||||||
0
app/models/base.py
Normal file
0
app/models/base.py
Normal file
11
app/models/material.py
Normal file
11
app/models/material.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from app.extensions import db
|
||||||
|
|
||||||
|
class MaterialBase(db.Model):
|
||||||
|
__tablename__ = 'material_base'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sku_code = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
name = db.Column(db.String(255), nullable=False)
|
||||||
|
spec_model = db.Column(db.String(255))
|
||||||
|
unit = db.Column(db.String(50))
|
||||||
|
# 其他字段按需添加,入库时主要是为了外键关联
|
||||||
25
app/models/stock.py
Normal file
25
app/models/stock.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from app.extensions import db
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class StockBuy(db.Model):
|
||||||
|
__tablename__ = 'stock_buy'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
material_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
|
||||||
|
inbound_date = db.Column(db.DateTime, default=datetime.now)
|
||||||
|
barcode = db.Column(db.String(100))
|
||||||
|
batch_no = db.Column(db.String(100))
|
||||||
|
|
||||||
|
# 数量相关 (使用 Numeric 对应数据库的 NUMERIC)
|
||||||
|
qty_inbound = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
qty_current = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
qty_available = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
|
||||||
|
price_unit = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
price_total = db.Column(db.Numeric(19, 4), default=0)
|
||||||
|
supplier_name = db.Column(db.String(255))
|
||||||
|
warehouse_loc = db.Column(db.String(100))
|
||||||
|
|
||||||
|
# 建立关联,方便查询物料详情
|
||||||
|
material = db.relationship('MaterialBase', backref='buy_stocks')
|
||||||
0
app/models/system.py
Normal file
0
app/models/system.py
Normal file
0
app/models/transaction.py
Normal file
0
app/models/transaction.py
Normal file
0
app/schemas/__init__.py
Normal file
0
app/schemas/__init__.py
Normal file
0
app/schemas/material_schema.py
Normal file
0
app/schemas/material_schema.py
Normal file
14
app/schemas/stock_schema.py
Normal file
14
app/schemas/stock_schema.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from app.extensions import ma
|
||||||
|
from app.models.stock import StockBuy
|
||||||
|
from marshmallow import fields
|
||||||
|
|
||||||
|
class StockBuySchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = StockBuy
|
||||||
|
load_instance = True # 反序列化时自动创建模型实例
|
||||||
|
include_fk = True # 包含外键 material_id
|
||||||
|
|
||||||
|
# 必须字段校验
|
||||||
|
material_id = fields.Integer(required=True)
|
||||||
|
qty_inbound = fields.Decimal(required=True, as_string=True)
|
||||||
|
price_unit = fields.Decimal(missing=0, as_string=True)
|
||||||
0
app/schemas/system_schema.py
Normal file
0
app/schemas/system_schema.py
Normal file
0
app/schemas/transaction_schema.py
Normal file
0
app/schemas/transaction_schema.py
Normal file
0
app/services/__init__.py
Normal file
0
app/services/__init__.py
Normal file
0
app/services/auth_service.py
Normal file
0
app/services/auth_service.py
Normal file
0
app/services/material_service.py
Normal file
0
app/services/material_service.py
Normal file
39
app/services/stock_service.py
Normal file
39
app/services/stock_service.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from app.extensions import db
|
||||||
|
from app.models.stock import StockBuy
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
|
||||||
|
def create_inbound_stock(data):
|
||||||
|
"""
|
||||||
|
处理采购入库逻辑
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 计算总价
|
||||||
|
qty = data.get('qty_inbound')
|
||||||
|
price = data.get('price_unit', 0)
|
||||||
|
total = float(qty) * float(price)
|
||||||
|
|
||||||
|
# 2. 创建库存记录
|
||||||
|
# 注意:入库时,当前库存(current)和可用库存(available)通常等于入库数量
|
||||||
|
new_stock = StockBuy(
|
||||||
|
material_id=data['material_id'],
|
||||||
|
barcode=data.get('barcode'),
|
||||||
|
batch_no=data.get('batch_no'),
|
||||||
|
qty_inbound=qty,
|
||||||
|
qty_current=qty, # 初始:当前=入库
|
||||||
|
qty_available=qty, # 初始:可用=入库
|
||||||
|
price_unit=price,
|
||||||
|
price_total=total,
|
||||||
|
supplier_name=data.get('supplier_name'),
|
||||||
|
warehouse_loc=data.get('warehouse_loc'),
|
||||||
|
inbound_date=data.get('inbound_date') # 如果前端没传,Model会默认用当前时间
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(new_stock)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return new_stock
|
||||||
|
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
db.session.rollback()
|
||||||
|
raise e
|
||||||
0
app/services/trans_service.py
Normal file
0
app/services/trans_service.py
Normal file
0
app/utils/__init__.py
Normal file
0
app/utils/__init__.py
Normal file
0
app/utils/decorators.py
Normal file
0
app/utils/decorators.py
Normal file
0
app/utils/helpers.py
Normal file
0
app/utils/helpers.py
Normal file
14
config.py
Normal file
14
config.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
# 数据库连接配置
|
||||||
|
# 请务必将 '你的密码' 替换为你 PostgreSQL 的真实密码
|
||||||
|
# 如果数据库不在本地,请将 localhost 替换为 IP 地址
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:1234@localhost:5432/inventory_system'
|
||||||
|
|
||||||
|
# 关闭 SQLAlchemy 的事件追踪,减少内存消耗
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
# Flask 的密钥,用于 Session 加密等,开发环境随便写一个即可
|
||||||
|
SECRET_KEY = 'dev-secret-key-1234'
|
||||||
8
requirements.txt
Normal file
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
Flask-SQLAlchemy==3.1.1
|
||||||
|
Flask-Migrate==4.0.5
|
||||||
|
Flask-Marshmallow==1.1.0
|
||||||
|
marshmallow-sqlalchemy==1.0.0
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
flask-cors==4.0.0
|
||||||
Reference in New Issue
Block a user