采购件,半成品,产品页面初步完成

This commit is contained in:
dxc
2026-01-29 09:27:56 +08:00
parent b0df5c7458
commit 06ba2d7563
11 changed files with 836 additions and 244 deletions

View File

@ -1,6 +1,5 @@
# app/models/buy.py
# app/models/inbound/buy.py
from app.extensions import db
from datetime import datetime
class StockBuy(db.Model):
@ -10,74 +9,54 @@ class StockBuy(db.Model):
"""
__tablename__ = 'stock_buy'
# 主键
id = db.Column(db.Integer, primary_key=True)
# 【核心关联】外键关联 material_base 表
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
# --- 身份标识 ---
# 身份标识
sku = db.Column(db.String(100))
in_date = db.Column(db.Date)
barcode = db.Column(db.String(100)) # 条码
serial_number = db.Column(db.String(100)) # 序列号
batch_number = db.Column(db.String(100)) # 批号
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))
batch_number = db.Column(db.String(100))
# --- 数量 ---
# 状态
status = db.Column(db.String(50))
inspection_status = db.Column(db.String(50))
warehouse_location = db.Column(db.String(100))
# 数量
in_quantity = db.Column(db.Numeric(19, 4), default=0)
stock_quantity = db.Column(db.Numeric(19, 4), default=0)
available_quantity = db.Column(db.Numeric(19, 4), default=0)
# --- 状态与位置 ---
status = db.Column(db.String(50)) # 在库/出库/损耗
inspection_status = db.Column(db.String(50)) # 未检/合格/不合格
warehouse_location = db.Column(db.String(100))
# --- 财务与商务 ---
# 财务与商务
unit_price = db.Column(db.Numeric(19, 4), default=0)
total_price = db.Column(db.Numeric(19, 4), default=0)
currency = db.Column(db.String(20), default='CNY')
exchange_rate = db.Column(db.Numeric(15, 6), default=1.0)
supplier_name = db.Column(db.String(255))
# [关键映射区]Python属性名 = DB列名
# 前端传 purchaser -> 存入 buyer_name
buyer_name = db.Column(db.String(100))
# 前端传 purchaser_email -> 存入 buyer_email
buyer_email = db.Column(db.String(100))
# 前端传 source_link -> 存入 original_link
original_link = db.Column(db.Text)
buyer_name = db.Column(db.String(100)) # 对应 SQL: buyer_name
buyer_email = db.Column(db.String(100)) # 对应 SQL: buyer_email
original_link = db.Column(db.Text) # 对应 SQL: original_link
detail_link = db.Column(db.Text)
arrival_photo = db.Column(db.Text)
# [这就是报错缺失的字段],请确保执行了 ALTER TABLE
remark = db.Column(db.Text)
# 注意SQL 中没有 remark 字段,这里已移除
# 关系定义
# 建立与 MaterialBase 的关系,方便通过 stock.material 访问基础信息
# 关系定义
material = db.relationship('MaterialBase', back_populates='stock_buys')
def to_dict(self):
"""
序列化:将模型转换为字典,主要用于单条查询或内部调用
列表查询主要依赖 Service 层的手动构建以提高性能
"""
return {
'id': self.id,
'base_id': self.base_id,
# 级联基础信息 (防止 None 报错)
'material_name': self.material.name if self.material else '',
'spec_model': self.material.spec_model if self.material else '',
'category': self.material.category if self.material else '',
'unit': self.material.unit if self.material else '',
'material_type': self.material.material_type if self.material else '',
# 实体信息
'sku': self.sku,
'inbound_date': self.in_date.strftime('%Y-%m-%d') if self.in_date else '',
'barcode': self.barcode,
@ -86,27 +65,23 @@ class StockBuy(db.Model):
'warehouse_loc': self.warehouse_location,
'status': self.status,
'inspection_status': self.inspection_status,
'remark': self.remark,
# 数量 (转为float防止json序列化报错)
'in_quantity': float(self.in_quantity or 0),
'qty_inbound': float(self.in_quantity or 0), # 兼容字段
'qty_inbound': float(self.in_quantity or 0),
'stock_quantity': float(self.stock_quantity or 0),
'qty_stock': float(self.stock_quantity or 0), # 兼容字段
'qty_stock': float(self.stock_quantity or 0),
'available_quantity': float(self.available_quantity or 0),
'qty_available': float(self.available_quantity or 0), # 兼容字段
'qty_available': float(self.available_quantity or 0),
# 财务
'unit_price': float(self.unit_price or 0),
'total_price': float(self.total_price or 0),
'currency': self.currency,
'exchange_rate': float(self.exchange_rate or 1.0),
# 商务 (字段映射)
'supplier_name': self.supplier_name,
'purchaser': self.buyer_name, # 映射回前端
'purchaser_email': self.buyer_email, # 映射回前端
'source_link': self.original_link, # 映射回前端
'purchaser': self.buyer_name,
'purchaser_email': self.buyer_email,
'source_link': self.original_link,
'detail_link': self.detail_link,
'arrival_photo': self.arrival_photo
}

View File

@ -0,0 +1,102 @@
# app/models/inbound/product.py
from app.extensions import db
class StockProduct(db.Model):
"""
成品入库库存表
对应数据库表: stock_product
"""
__tablename__ = 'stock_product'
id = db.Column(db.Integer, primary_key=True)
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
sku = db.Column(db.String(100))
production_date = db.Column(db.Date)
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))
# SQL 无 batch_number
# 数量
in_quantity = db.Column(db.Numeric(19, 4), default=0)
stock_quantity = db.Column(db.Numeric(19, 4), default=0)
available_quantity = db.Column(db.Numeric(19, 4), default=0)
# 状态与位置
status = db.Column(db.String(50))
warehouse_location = db.Column(db.String(100))
# 生产与成本
bom_code = db.Column('bom_id', db.String(100)) # 映射 SQL: bom_id
bom_version = db.Column(db.String(50))
work_order_code = db.Column('work_order_id', db.String(100)) # 映射 SQL: work_order_id
raw_material_cost = db.Column(db.Numeric(19, 4), default=0)
manual_cost = db.Column(db.Numeric(19, 4), default=0)
production_manager = db.Column('producer_name', db.String(100)) # 映射 SQL: producer_name
production_time_range = db.Column(db.String(255))
# 质量与链接
quality_status = db.Column(db.String(50))
quality_report_link = db.Column(db.Text)
detail_link = db.Column(db.Text)
# 成品特有字段
sale_price = db.Column(db.Numeric(19, 4), default=0)
inspection_report_link = db.Column(db.Text)
order_id = db.Column(db.String(100))
# 关系定义
material = db.relationship('MaterialBase', back_populates='stock_products')
def to_dict(self):
raw_val = float(self.raw_material_cost or 0)
man_val = float(self.manual_cost or 0)
unit_total = raw_val + man_val
return {
'id': self.id,
'base_id': self.base_id,
'material_name': self.material.name if self.material else '',
'spec_model': self.material.spec_model if self.material else '',
'category': self.material.category if self.material else '',
'unit': self.material.unit if self.material else '',
'material_type': self.material.material_type if self.material else '',
'sku': self.sku,
'inbound_date': self.production_date.strftime('%Y-%m-%d') if self.production_date else '',
'barcode': self.barcode,
'serial_number': self.serial_number,
'warehouse_loc': self.warehouse_location,
'status': self.status,
'in_quantity': float(self.in_quantity or 0),
'qty_inbound': float(self.in_quantity or 0),
'stock_quantity': float(self.stock_quantity or 0),
'qty_stock': float(self.stock_quantity or 0),
'available_quantity': float(self.available_quantity or 0),
'qty_available': float(self.available_quantity or 0),
'bom_code': self.bom_code,
'bom_version': self.bom_version,
'work_order_code': self.work_order_code,
'raw_material_cost': raw_val,
'manual_cost': man_val,
'unit_total_cost': unit_total,
'production_manager': self.production_manager,
'production_time_range': self.production_time_range,
'production_start_time': self.production_time_range.split(' ~ ')[
0] if self.production_time_range and ' ~ ' in self.production_time_range else '',
'production_end_time': self.production_time_range.split(' ~ ')[
1] if self.production_time_range and ' ~ ' in self.production_time_range else '',
'quality_status': self.quality_status,
'quality_report_link': self.quality_report_link,
'detail_link': self.detail_link,
'sale_price': float(self.sale_price or 0),
'inspection_report_link': self.inspection_report_link,
'order_id': self.order_id
}

View File

@ -1,6 +1,5 @@
# app/models/inbound/semi.py
from app.extensions import db
from datetime import datetime
class StockSemi(db.Model):
@ -10,80 +9,43 @@ class StockSemi(db.Model):
"""
__tablename__ = 'stock_semi'
# =========================================================
# 1. 基础字段 (Strictly matching SQL Schema)
# =========================================================
# 主键
id = db.Column(db.Integer, primary_key=True)
# 外键关联 material_base 表
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
# 身份标识
sku = db.Column(db.String(100))
# SQL字段名为 production_date, 对应前端的 "入库日期/生产日期"
production_date = db.Column(db.Date)
barcode = db.Column(db.String(100))
serial_number = db.Column(db.String(100))
# SQL 无 batch_number此处移除
barcode = db.Column(db.String(100)) # 条码
serial_number = db.Column(db.String(100)) # 序列号
# 注意:提供的 SQL 中 stock_semi 没有 batch_number 字段,这里不定义,以免报错。
# 如果后续数据库加上了该字段,请取消下方注释:
# batch_number = db.Column(db.String(100))
# --- 数量 ---
# 数量
in_quantity = db.Column(db.Numeric(19, 4), default=0)
stock_quantity = db.Column(db.Numeric(19, 4), default=0)
available_quantity = db.Column(db.Numeric(19, 4), default=0)
# --- 状态与位置 ---
status = db.Column(db.String(50)) # 在库/出库/损耗
warehouse_location = db.Column(db.String(100)) # 仓库位
# 状态与位置
status = db.Column(db.String(50))
warehouse_location = db.Column(db.String(100))
# =========================================================
# 2. 半成品特有字段 (SQL 字段映射)
# =========================================================
# BOM 相关
# 数据库列名: bom_id, Python属性: bom_code (为了适配前端习惯)
bom_code = db.Column('bom_id', db.String(100))
# 半成品特有字段 (SQL 字段映射)
bom_code = db.Column('bom_id', db.String(100)) # 映射 SQL: bom_id
bom_version = db.Column(db.String(50))
work_order_code = db.Column('work_order_id', db.String(100)) # 映射 SQL: work_order_id
# 工单 相关
# 数据库列名: work_order_id, Python属性: work_order_code
work_order_code = db.Column('work_order_id', db.String(100))
raw_material_cost = db.Column(db.Numeric(19, 4), default=0)
manual_cost = db.Column(db.Numeric(19, 4), default=0)
# 成本 相关
raw_material_cost = db.Column(db.Numeric(19, 4), default=0) # 原材料成本
manual_cost = db.Column(db.Numeric(19, 4), default=0) # 手动/人工成本
# 生产信息
# 数据库列名: producer_name, Python属性: production_manager
production_manager = db.Column('producer_name', db.String(100))
# 生产起止时间 (SQL定义为 VARCHAR(255))
production_manager = db.Column('producer_name', db.String(100)) # 映射 SQL: producer_name
production_time_range = db.Column(db.String(255))
# 质量与链接
quality_status = db.Column(db.String(50)) # 质量状态
quality_report_link = db.Column(db.Text) # 质量报告链接
detail_link = db.Column(db.Text) # 详细信息链接
quality_status = db.Column(db.String(50))
quality_report_link = db.Column(db.Text)
detail_link = db.Column(db.Text)
# =========================================================
# 3. 关系定义
# =========================================================
# 建立与 MaterialBase 的关系
# 注意:确保 MaterialBase 模型中定义了 back_populates='stock_semis'
# 关系定义
material = db.relationship('MaterialBase', back_populates='stock_semis')
def to_dict(self):
"""
序列化将模型转换为字典供API返回JSON使用
在这里处理字段名称转换,确保前端能正确显示数据
"""
# 计算单件总成本 (原料 + 人工)
raw_val = float(self.raw_material_cost or 0)
man_val = float(self.manual_cost or 0)
unit_total = raw_val + man_val
@ -91,47 +53,35 @@ class StockSemi(db.Model):
return {
'id': self.id,
'base_id': self.base_id,
# --- 级联基础信息 (防止 None 报错) ---
'material_name': self.material.name if self.material else '',
'spec_model': self.material.spec_model if self.material else '',
'category': self.material.category if self.material else '',
'unit': self.material.unit if self.material else '',
'material_type': self.material.material_type if self.material else '',
# --- 实体信息 ---
'sku': self.sku,
# 将 production_date 映射回前端通用的 inbound_date
'inbound_date': self.production_date.strftime('%Y-%m-%d') if self.production_date else '',
'barcode': self.barcode,
'serial_number': self.serial_number,
# 'batch_number': self.batch_number, # SQL无此字段暂不返回
'warehouse_loc': self.warehouse_location,
'status': self.status,
# --- 数量 (转为float防止json序列化报错) ---
'in_quantity': float(self.in_quantity or 0),
'qty_inbound': float(self.in_quantity or 0), # 兼容字段
'qty_inbound': float(self.in_quantity or 0),
'stock_quantity': float(self.stock_quantity or 0),
'qty_stock': float(self.stock_quantity or 0), # 兼容字段
'qty_stock': float(self.stock_quantity or 0),
'available_quantity': float(self.available_quantity or 0),
'qty_available': float(self.available_quantity or 0), # 兼容字段
'qty_available': float(self.available_quantity or 0),
# --- 半成品特有数据 ---
'bom_code': self.bom_code,
'bom_version': self.bom_version,
'work_order_code': self.work_order_code,
'raw_material_cost': raw_val,
'manual_cost': man_val,
'unit_total_cost': unit_total, # 前端展示总成本用
'unit_total_cost': unit_total,
'production_manager': self.production_manager,
# 时间范围 (SQL存的是字符串直接返回即可或者根据需要拆分)
# 如果 service 层存的是 "Start ~ End",这里直接返回
'production_time_range': self.production_time_range,
# 为了兼容前端分开的时间字段(如果有):
# 简单的时间拆分逻辑,防止 split 报错
'production_start_time': self.production_time_range.split(' ~ ')[
0] if self.production_time_range and ' ~ ' in self.production_time_range else '',
'production_end_time': self.production_time_range.split(' ~ ')[