入库操作

This commit is contained in:
dxc
2026-01-26 13:35:30 +08:00
parent 0081e7682d
commit 65d9e89c9a
33 changed files with 193 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
venv/
pgdata/
__pycache__/
*.pyc
.env
.idea/
.vscode/
.DS_Store

26
app/__init__.py Normal file
View 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
View File

0
app/api/v1/__init__.py Normal file
View File

0
app/api/v1/auth.py Normal file
View File

0
app/api/v1/materials.py Normal file
View File

0
app/api/v1/reports.py Normal file
View File

34
app/api/v1/stocks.py Normal file
View 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

View File

6
app/extensions.py Normal file
View 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
View File

@ -0,0 +1,2 @@
from app.models.material import MaterialBase
from app.models.stock import StockBuy

0
app/models/base.py Normal file
View File

11
app/models/material.py Normal file
View 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
View 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
View File

View File

0
app/schemas/__init__.py Normal file
View File

View File

View 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)

View File

View File

0
app/services/__init__.py Normal file
View File

View File

View File

View 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

View File

0
app/utils/__init__.py Normal file
View File

0
app/utils/decorators.py Normal file
View File

0
app/utils/helpers.py Normal file
View File

14
config.py Normal file
View 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
View 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

6
run.py Normal file
View File

@ -0,0 +1,6 @@
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)