入库操作
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