From 65d9e89c9abc65bff8324a64eb82809f9d96524d Mon Sep 17 00:00:00 2001 From: dxc Date: Mon, 26 Jan 2026 13:35:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A5=E5=BA=93=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 +++++ app/__init__.py | 26 +++++++++++++++ test占位文件.py => app/api/__init__.py | 0 app/api/errors.py | 0 app/api/v1/__init__.py | 0 app/api/v1/auth.py | 0 app/api/v1/materials.py | 0 app/api/v1/reports.py | 0 app/api/v1/stocks.py | 34 +++++++++++++++++++ app/api/v1/transactions.py | 0 app/extensions.py | 6 ++++ app/models/__init__.py | 2 ++ app/models/base.py | 0 app/models/material.py | 11 ++++++ app/models/stock.py | 25 ++++++++++++++ app/models/system.py | 0 app/models/transaction.py | 0 app/schemas/__init__.py | 0 app/schemas/material_schema.py | 0 app/schemas/stock_schema.py | 14 ++++++++ app/schemas/system_schema.py | 0 app/schemas/transaction_schema.py | 0 app/services/__init__.py | 0 app/services/auth_service.py | 0 app/services/material_service.py | 0 app/services/stock_service.py | 39 ++++++++++++++++++++++ app/services/trans_service.py | 0 app/utils/__init__.py | 0 app/utils/decorators.py | 0 app/utils/helpers.py | 0 config.py | 14 ++++++++ requirements.txt | 8 +++++ run.py | 6 ++++ 33 files changed, 193 insertions(+) create mode 100644 .gitignore create mode 100644 app/__init__.py rename test占位文件.py => app/api/__init__.py (100%) create mode 100644 app/api/errors.py create mode 100644 app/api/v1/__init__.py create mode 100644 app/api/v1/auth.py create mode 100644 app/api/v1/materials.py create mode 100644 app/api/v1/reports.py create mode 100644 app/api/v1/stocks.py create mode 100644 app/api/v1/transactions.py create mode 100644 app/extensions.py create mode 100644 app/models/__init__.py create mode 100644 app/models/base.py create mode 100644 app/models/material.py create mode 100644 app/models/stock.py create mode 100644 app/models/system.py create mode 100644 app/models/transaction.py create mode 100644 app/schemas/__init__.py create mode 100644 app/schemas/material_schema.py create mode 100644 app/schemas/stock_schema.py create mode 100644 app/schemas/system_schema.py create mode 100644 app/schemas/transaction_schema.py create mode 100644 app/services/__init__.py create mode 100644 app/services/auth_service.py create mode 100644 app/services/material_service.py create mode 100644 app/services/stock_service.py create mode 100644 app/services/trans_service.py create mode 100644 app/utils/__init__.py create mode 100644 app/utils/decorators.py create mode 100644 app/utils/helpers.py create mode 100644 config.py create mode 100644 requirements.txt create mode 100644 run.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30743f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +venv/ +pgdata/ +__pycache__/ +*.pyc +.env +.idea/ +.vscode/ +.DS_Store \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..621481e --- /dev/null +++ b/app/__init__.py @@ -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 \ No newline at end of file diff --git a/test占位文件.py b/app/api/__init__.py similarity index 100% rename from test占位文件.py rename to app/api/__init__.py diff --git a/app/api/errors.py b/app/api/errors.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/auth.py b/app/api/v1/auth.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/materials.py b/app/api/v1/materials.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/reports.py b/app/api/v1/reports.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/stocks.py b/app/api/v1/stocks.py new file mode 100644 index 0000000..8d83b4c --- /dev/null +++ b/app/api/v1/stocks.py @@ -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 \ No newline at end of file diff --git a/app/api/v1/transactions.py b/app/api/v1/transactions.py new file mode 100644 index 0000000..e69de29 diff --git a/app/extensions.py b/app/extensions.py new file mode 100644 index 0000000..412073c --- /dev/null +++ b/app/extensions.py @@ -0,0 +1,6 @@ +from flask_sqlalchemy import SQLAlchemy +from flask_marshmallow import Marshmallow + +# 初始化数据库和序列化工具 +db = SQLAlchemy() +ma = Marshmallow() \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..be6d6a3 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,2 @@ +from app.models.material import MaterialBase +from app.models.stock import StockBuy \ No newline at end of file diff --git a/app/models/base.py b/app/models/base.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/material.py b/app/models/material.py new file mode 100644 index 0000000..bd25964 --- /dev/null +++ b/app/models/material.py @@ -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)) + # 其他字段按需添加,入库时主要是为了外键关联 \ No newline at end of file diff --git a/app/models/stock.py b/app/models/stock.py new file mode 100644 index 0000000..d234692 --- /dev/null +++ b/app/models/stock.py @@ -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') \ No newline at end of file diff --git a/app/models/system.py b/app/models/system.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/transaction.py b/app/models/transaction.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/material_schema.py b/app/schemas/material_schema.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/stock_schema.py b/app/schemas/stock_schema.py new file mode 100644 index 0000000..e790622 --- /dev/null +++ b/app/schemas/stock_schema.py @@ -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) \ No newline at end of file diff --git a/app/schemas/system_schema.py b/app/schemas/system_schema.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/transaction_schema.py b/app/schemas/transaction_schema.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/auth_service.py b/app/services/auth_service.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/material_service.py b/app/services/material_service.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/stock_service.py b/app/services/stock_service.py new file mode 100644 index 0000000..0c34d9d --- /dev/null +++ b/app/services/stock_service.py @@ -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 \ No newline at end of file diff --git a/app/services/trans_service.py b/app/services/trans_service.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/decorators.py b/app/utils/decorators.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/helpers.py b/app/utils/helpers.py new file mode 100644 index 0000000..e69de29 diff --git a/config.py b/config.py new file mode 100644 index 0000000..1df7e5e --- /dev/null +++ b/config.py @@ -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' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7e7294d --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..7425f4c --- /dev/null +++ b/run.py @@ -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) \ No newline at end of file