2026-03-10 17:33:14 +08:00
|
|
|
|
from app.extensions import db, beijing_time
|
2026-02-04 13:30:07 +08:00
|
|
|
|
from datetime import datetime
|
2026-02-28 11:43:10 +08:00
|
|
|
|
from sqlalchemy import func
|
2026-02-04 13:30:07 +08:00
|
|
|
|
|
2026-02-04 14:29:59 +08:00
|
|
|
|
|
2026-02-04 13:30:07 +08:00
|
|
|
|
class TransBorrow(db.Model):
|
|
|
|
|
|
__tablename__ = 'trans_borrow'
|
2026-02-04 14:29:59 +08:00
|
|
|
|
|
2026-02-04 13:30:07 +08:00
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2026-02-06 17:11:47 +08:00
|
|
|
|
borrow_no = db.Column(db.String(100))
|
|
|
|
|
|
sku = db.Column(db.String(100))
|
2026-02-04 13:30:07 +08:00
|
|
|
|
source_table = db.Column(db.String(50))
|
|
|
|
|
|
stock_id = db.Column(db.Integer)
|
2026-02-06 17:11:47 +08:00
|
|
|
|
barcode = db.Column(db.String(100))
|
2026-02-04 13:30:07 +08:00
|
|
|
|
quantity = db.Column(db.Numeric(19, 4))
|
2026-03-18 10:41:19 +08:00
|
|
|
|
returned_quantity = db.Column(db.Numeric(19, 4), default=0)
|
2026-02-06 17:11:47 +08:00
|
|
|
|
borrower_name = db.Column(db.String(100))
|
2026-03-10 17:33:14 +08:00
|
|
|
|
borrow_time = db.Column(db.DateTime, default=beijing_time)
|
2026-02-09 11:59:35 +08:00
|
|
|
|
borrow_signature = db.Column(db.Text)
|
2026-02-04 13:30:07 +08:00
|
|
|
|
expected_return_time = db.Column(db.DateTime)
|
2026-02-06 17:11:47 +08:00
|
|
|
|
is_returned = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
return_time = db.Column(db.DateTime)
|
2026-02-09 11:59:35 +08:00
|
|
|
|
return_operator = db.Column(db.String(100))
|
|
|
|
|
|
return_signature = db.Column(db.Text)
|
|
|
|
|
|
return_location = db.Column(db.String(100))
|
2026-02-04 14:29:59 +08:00
|
|
|
|
status = db.Column(db.String(20), default='borrowed')
|
2026-02-06 17:11:47 +08:00
|
|
|
|
remark = db.Column(db.Text)
|
2026-02-04 13:30:07 +08:00
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
2026-03-18 10:41:19 +08:00
|
|
|
|
returned_qty = float(self.returned_quantity) if self.returned_quantity is not None else 0
|
|
|
|
|
|
total_qty = float(self.quantity) if self.quantity is not None else 0
|
|
|
|
|
|
pending_qty = total_qty - returned_qty
|
2026-02-04 13:30:07 +08:00
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
2026-02-06 17:11:47 +08:00
|
|
|
|
'borrow_no': self.borrow_no,
|
2026-02-04 13:30:07 +08:00
|
|
|
|
'sku': self.sku,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'source_table': self.source_table,
|
|
|
|
|
|
'stock_id': self.stock_id,
|
2026-02-06 17:11:47 +08:00
|
|
|
|
'barcode': self.barcode,
|
2026-03-18 10:41:19 +08:00
|
|
|
|
'quantity': total_qty,
|
|
|
|
|
|
'returned_quantity': returned_qty,
|
|
|
|
|
|
'pending_quantity': pending_qty,
|
2026-02-04 13:30:07 +08:00
|
|
|
|
'borrower_name': self.borrower_name,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'borrow_time': self.borrow_time.strftime('%Y-%m-%d %H:%M') if self.borrow_time else None,
|
2026-02-06 17:11:47 +08:00
|
|
|
|
'borrow_signature': self.borrow_signature,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'expected_return_time': self.expected_return_time.strftime('%Y-%m-%d %H:%M') if self.expected_return_time else None,
|
2026-02-06 17:11:47 +08:00
|
|
|
|
'is_returned': self.is_returned,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'return_time': self.return_time.strftime('%Y-%m-%d %H:%M') if self.return_time else None,
|
2026-02-06 17:11:47 +08:00
|
|
|
|
'return_operator': self.return_operator,
|
|
|
|
|
|
'return_signature': self.return_signature,
|
|
|
|
|
|
'return_location': self.return_location,
|
|
|
|
|
|
'status': self.status,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'remark': self.remark,
|
2026-02-09 11:44:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 11:43:10 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
|
def get_borrowed_quantity(cls, source_table, stock_id):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取指定库存记录(source_table 和 stock_id)的借出未还数量总和。
|
|
|
|
|
|
返回浮点数,若无借出记录则返回 0.0。
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = db.session.query(func.sum(cls.quantity)).filter(
|
|
|
|
|
|
cls.source_table == source_table,
|
|
|
|
|
|
cls.stock_id == stock_id,
|
|
|
|
|
|
cls.is_returned == False
|
|
|
|
|
|
).scalar()
|
|
|
|
|
|
return float(result) if result is not None else 0.0
|
|
|
|
|
|
|
2026-02-09 11:44:24 +08:00
|
|
|
|
|
|
|
|
|
|
class TransRepair(db.Model):
|
|
|
|
|
|
__tablename__ = 'trans_repair'
|
|
|
|
|
|
|
2026-04-08 18:23:22 +08:00
|
|
|
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 维修单号 (新增)
|
|
|
|
|
|
repair_no = db.Column(db.String(50), nullable=True, unique=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 关联基础信息 (新增)
|
|
|
|
|
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
# SKU 保留
|
2026-02-09 11:44:24 +08:00
|
|
|
|
sku = db.Column(db.String(100))
|
2026-04-08 18:23:22 +08:00
|
|
|
|
|
2026-04-08 19:36:14 +08:00
|
|
|
|
# 物料名称 (独立录入时使用,非关联base_id)
|
|
|
|
|
|
material_name = db.Column(db.String(200))
|
|
|
|
|
|
|
2026-04-08 18:23:22 +08:00
|
|
|
|
# 序列号SN (新增,用于单台追溯)
|
|
|
|
|
|
serial_number = db.Column(db.String(100), nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 来源追溯 (兼容旧数据)
|
2026-02-09 11:44:24 +08:00
|
|
|
|
source_table = db.Column(db.String(50))
|
|
|
|
|
|
stock_id = db.Column(db.Integer)
|
2026-02-09 11:59:35 +08:00
|
|
|
|
is_self_made = db.Column(db.Boolean, default=False)
|
|
|
|
|
|
related_product_id = db.Column(db.Integer)
|
2026-04-08 18:23:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 入库/接收时间
|
|
|
|
|
|
arrival_date = db.Column(db.Date)
|
|
|
|
|
|
|
|
|
|
|
|
# 维修状态 (新增)
|
|
|
|
|
|
repair_status = db.Column(db.String(50), default='待检测')
|
|
|
|
|
|
|
|
|
|
|
|
# 客户反馈
|
2026-02-09 11:59:35 +08:00
|
|
|
|
fault_description = db.Column(db.Text)
|
2026-04-08 18:23:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 预计修复时间
|
|
|
|
|
|
expected_repair_time = db.Column(db.String(100))
|
|
|
|
|
|
|
|
|
|
|
|
# 维修日志/结果
|
2026-02-09 11:59:35 +08:00
|
|
|
|
repair_result = db.Column(db.Text)
|
2026-04-08 18:23:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 维修人
|
|
|
|
|
|
repair_manager = db.Column(db.String(100))
|
|
|
|
|
|
|
|
|
|
|
|
# 出库交付时间
|
|
|
|
|
|
shipping_date = db.Column(db.Date)
|
|
|
|
|
|
|
|
|
|
|
|
# 客户名/来源
|
|
|
|
|
|
related_contract_id = db.Column(db.String(100))
|
|
|
|
|
|
|
2026-04-08 19:36:14 +08:00
|
|
|
|
# 客户名称 (新增)
|
|
|
|
|
|
customer_name = db.Column(db.String(100))
|
|
|
|
|
|
|
|
|
|
|
|
# 客户所在地 (新增)
|
|
|
|
|
|
customer_location = db.Column(db.String(255))
|
|
|
|
|
|
|
2026-04-08 18:23:22 +08:00
|
|
|
|
# 成本与售价
|
2026-02-09 11:59:35 +08:00
|
|
|
|
cost_price = db.Column(db.Numeric(19, 4))
|
|
|
|
|
|
sale_price = db.Column(db.Numeric(19, 4))
|
|
|
|
|
|
|
2026-04-08 18:23:22 +08:00
|
|
|
|
# 数据隔离 (新增)
|
|
|
|
|
|
company_id = db.Column(db.Integer, nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 关联关系
|
|
|
|
|
|
base = db.relationship('MaterialBase', backref='repairs')
|
|
|
|
|
|
|
2026-02-09 11:44:24 +08:00
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
2026-04-08 18:23:22 +08:00
|
|
|
|
'repair_no': self.repair_no,
|
|
|
|
|
|
'base_id': self.base_id,
|
2026-02-09 11:44:24 +08:00
|
|
|
|
'sku': self.sku,
|
2026-04-08 19:36:14 +08:00
|
|
|
|
'material_name': self.material_name,
|
2026-04-08 18:23:22 +08:00
|
|
|
|
'serial_number': self.serial_number,
|
2026-02-09 11:44:24 +08:00
|
|
|
|
'source_table': self.source_table,
|
|
|
|
|
|
'stock_id': self.stock_id,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'arrival_date': self.arrival_date.strftime('%Y-%m-%d') if self.arrival_date else None,
|
2026-04-08 18:23:22 +08:00
|
|
|
|
'repair_status': self.repair_status,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'expected_repair_time': self.expected_repair_time,
|
|
|
|
|
|
'shipping_date': self.shipping_date.strftime('%Y-%m-%d') if self.shipping_date else None,
|
|
|
|
|
|
'is_self_made': self.is_self_made,
|
|
|
|
|
|
'related_product_id': self.related_product_id,
|
|
|
|
|
|
'related_contract_id': self.related_contract_id,
|
2026-04-08 19:36:14 +08:00
|
|
|
|
'customer_name': self.customer_name,
|
|
|
|
|
|
'customer_location': self.customer_location,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'repair_manager': self.repair_manager,
|
2026-02-09 11:44:24 +08:00
|
|
|
|
'fault_description': self.fault_description,
|
|
|
|
|
|
'repair_result': self.repair_result,
|
|
|
|
|
|
'cost_price': float(self.cost_price) if self.cost_price is not None else None,
|
|
|
|
|
|
'sale_price': float(self.sale_price) if self.sale_price is not None else None,
|
2026-04-08 18:23:22 +08:00
|
|
|
|
'company_id': self.company_id,
|
2026-02-09 11:44:24 +08:00
|
|
|
|
}
|
2026-02-09 11:50:03 +08:00
|
|
|
|
|
2026-02-09 11:59:35 +08:00
|
|
|
|
|
2026-02-09 11:50:03 +08:00
|
|
|
|
class TransScrap(db.Model):
|
|
|
|
|
|
__tablename__ = 'trans_scrap'
|
|
|
|
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
|
|
sku = db.Column(db.String(100))
|
|
|
|
|
|
source_table = db.Column(db.String(50))
|
|
|
|
|
|
stock_id = db.Column(db.Integer)
|
|
|
|
|
|
quantity = db.Column(db.Numeric(19, 4))
|
|
|
|
|
|
reason = db.Column(db.Text)
|
|
|
|
|
|
operator_name = db.Column(db.String(100))
|
2026-03-10 17:33:14 +08:00
|
|
|
|
operation_time = db.Column(db.DateTime, default=beijing_time)
|
2026-02-09 11:50:03 +08:00
|
|
|
|
approver_name = db.Column(db.String(100))
|
|
|
|
|
|
approval_status = db.Column(db.String(20), default='pending')
|
2026-02-09 11:59:35 +08:00
|
|
|
|
cost_at_scrap = db.Column(db.Numeric(19, 4))
|
|
|
|
|
|
total_loss = db.Column(db.Numeric(19, 4))
|
|
|
|
|
|
|
2026-02-09 11:50:03 +08:00
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': self.id,
|
|
|
|
|
|
'sku': self.sku,
|
|
|
|
|
|
'source_table': self.source_table,
|
|
|
|
|
|
'stock_id': self.stock_id,
|
|
|
|
|
|
'quantity': float(self.quantity) if self.quantity is not None else None,
|
|
|
|
|
|
'reason': self.reason,
|
|
|
|
|
|
'operator_name': self.operator_name,
|
2026-02-09 11:59:35 +08:00
|
|
|
|
'operation_time': self.operation_time.strftime('%Y-%m-%d %H:%M:%S') if self.operation_time else None,
|
2026-02-09 11:50:03 +08:00
|
|
|
|
'approver_name': self.approver_name,
|
|
|
|
|
|
'approval_status': self.approval_status,
|
|
|
|
|
|
'cost_at_scrap': float(self.cost_at_scrap) if self.cost_at_scrap is not None else None,
|
|
|
|
|
|
'total_loss': float(self.total_loss) if self.total_loss is not None else None,
|
|
|
|
|
|
}
|