Compare commits
14 Commits
646804bb98
...
2f140e112f
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f140e112f | |||
| 8264867b1c | |||
| d993e6796e | |||
| 4e05734865 | |||
| 7f19867139 | |||
| bcd39729f8 | |||
| 9cfbdc7d13 | |||
| d3510b0261 | |||
| 7b0082c6e0 | |||
| b08196c479 | |||
| 68ea351c99 | |||
| f001be9eef | |||
| 545cd86632 | |||
| b688480892 |
@ -14,6 +14,7 @@ import datetime
|
|||||||
# 需要 pip install openpyxl
|
# 需要 pip install openpyxl
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
|
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
class MaterialBaseService:
|
class MaterialBaseService:
|
||||||
@ -411,15 +412,59 @@ class MaterialBaseService:
|
|||||||
query_product = query_product.filter(cond)
|
query_product = query_product.filter(cond)
|
||||||
list_product = query_product.all()
|
list_product = query_product.all()
|
||||||
|
|
||||||
|
# 2.4 计算每个物料基础的最高单价/成本
|
||||||
|
buy_max = defaultdict(float)
|
||||||
|
for stock, base in list_buy:
|
||||||
|
p = float(stock.pre_tax_unit_price or 0)
|
||||||
|
if p > buy_max[base.id]:
|
||||||
|
buy_max[base.id] = p
|
||||||
|
|
||||||
|
semi_max = defaultdict(float)
|
||||||
|
for stock, base in list_semi:
|
||||||
|
p = float(stock.raw_material_cost or 0) + float(stock.manual_cost or 0)
|
||||||
|
if p > semi_max[base.id]:
|
||||||
|
semi_max[base.id] = p
|
||||||
|
|
||||||
|
product_max = defaultdict(float)
|
||||||
|
for stock, base in list_product:
|
||||||
|
p = float(stock.raw_material_cost or 0) + float(stock.manual_cost or 0)
|
||||||
|
if p > product_max[base.id]:
|
||||||
|
product_max[base.id] = p
|
||||||
|
|
||||||
|
highest_price = {}
|
||||||
|
all_base_ids = set()
|
||||||
|
for stock, base in list_buy:
|
||||||
|
all_base_ids.add(base.id)
|
||||||
|
for stock, base in list_semi:
|
||||||
|
all_base_ids.add(base.id)
|
||||||
|
for stock, base in list_product:
|
||||||
|
all_base_ids.add(base.id)
|
||||||
|
|
||||||
|
for base_id in all_base_ids:
|
||||||
|
price = None
|
||||||
|
buy_val = buy_max.get(base_id)
|
||||||
|
if buy_val is not None and buy_val > 0:
|
||||||
|
price = buy_val
|
||||||
|
else:
|
||||||
|
semi_val = semi_max.get(base_id)
|
||||||
|
if semi_val is not None and semi_val > 0:
|
||||||
|
price = semi_val
|
||||||
|
else:
|
||||||
|
prod_val = product_max.get(base_id)
|
||||||
|
if prod_val is not None and prod_val > 0:
|
||||||
|
price = prod_val
|
||||||
|
highest_price[base_id] = price or 0.0
|
||||||
|
|
||||||
# 3. 数据整合
|
# 3. 数据整合
|
||||||
all_rows = []
|
all_rows = []
|
||||||
|
|
||||||
# 处理采购件
|
# 处理采购件
|
||||||
for stock, base in list_buy:
|
for stock, base in list_buy:
|
||||||
# 价格计算
|
# 价格计算:使用当前物料基础的最高单价
|
||||||
unit_price = float(stock.pre_tax_unit_price or 0)
|
unit_price = highest_price.get(base.id, 0.0)
|
||||||
tax_rate = float(stock.tax_rate or 0)
|
tax_rate = float(stock.tax_rate or 0)
|
||||||
price_incl = float(stock.post_tax_unit_price or (unit_price * (1 + tax_rate / 100.0)))
|
# 根据新单价重新计算含税单价
|
||||||
|
price_incl = unit_price * (1 + tax_rate / 100.0)
|
||||||
qty = float(stock.stock_quantity or 0)
|
qty = float(stock.stock_quantity or 0)
|
||||||
|
|
||||||
# 计算不含税总价 = 数量 * 不含税单价
|
# 计算不含税总价 = 数量 * 不含税单价
|
||||||
@ -447,13 +492,14 @@ class MaterialBaseService:
|
|||||||
|
|
||||||
# 处理半成品
|
# 处理半成品
|
||||||
for stock, base in list_semi:
|
for stock, base in list_semi:
|
||||||
cost = float(stock.raw_material_cost or 0) + float(stock.manual_cost or 0)
|
# 使用当前物料基础的最高单价
|
||||||
|
unit_price = highest_price.get(base.id, 0.0)
|
||||||
qty = float(stock.stock_quantity or 0)
|
qty = float(stock.stock_quantity or 0)
|
||||||
|
|
||||||
# 半成品不含税总价 = 数量 * 成本
|
# 半成品不含税总价 = 数量 * 单价
|
||||||
total_val_excl = qty * cost
|
total_val_excl = qty * unit_price
|
||||||
# 含税总价同上 (税率0)
|
# 含税总价同上 (税率0)
|
||||||
total_val_incl = qty * cost
|
total_val_incl = qty * unit_price
|
||||||
|
|
||||||
ident = stock.batch_number or stock.serial_number or stock.barcode or stock.sku
|
ident = stock.batch_number or stock.serial_number or stock.barcode or stock.sku
|
||||||
|
|
||||||
@ -466,20 +512,21 @@ class MaterialBaseService:
|
|||||||
"date": stock.production_date,
|
"date": stock.production_date,
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
"avail": float(stock.available_quantity or 0),
|
"avail": float(stock.available_quantity or 0),
|
||||||
"price_excl": cost,
|
"price_excl": unit_price,
|
||||||
"total_val_excl": total_val_excl,
|
"total_val_excl": total_val_excl,
|
||||||
"tax": 0.0,
|
"tax": 0.0,
|
||||||
"price_incl": cost,
|
"price_incl": unit_price,
|
||||||
"total_val": total_val_incl
|
"total_val": total_val_incl
|
||||||
})
|
})
|
||||||
|
|
||||||
# 处理成品
|
# 处理成品
|
||||||
for stock, base in list_product:
|
for stock, base in list_product:
|
||||||
cost = float(stock.raw_material_cost or 0) + float(stock.manual_cost or 0)
|
# 使用当前物料基础的最高单价
|
||||||
|
unit_price = highest_price.get(base.id, 0.0)
|
||||||
qty = float(stock.stock_quantity or 0)
|
qty = float(stock.stock_quantity or 0)
|
||||||
|
|
||||||
total_val_excl = qty * cost
|
total_val_excl = qty * unit_price
|
||||||
total_val_incl = qty * cost
|
total_val_incl = qty * unit_price
|
||||||
|
|
||||||
ident = stock.serial_number or stock.barcode or stock.sku
|
ident = stock.serial_number or stock.barcode or stock.sku
|
||||||
|
|
||||||
@ -492,10 +539,10 @@ class MaterialBaseService:
|
|||||||
"date": stock.production_date,
|
"date": stock.production_date,
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
"avail": float(stock.available_quantity or 0),
|
"avail": float(stock.available_quantity or 0),
|
||||||
"price_excl": cost,
|
"price_excl": unit_price,
|
||||||
"total_val_excl": total_val_excl,
|
"total_val_excl": total_val_excl,
|
||||||
"tax": 0.0,
|
"tax": 0.0,
|
||||||
"price_incl": cost,
|
"price_incl": unit_price,
|
||||||
"total_val": total_val_incl
|
"total_val": total_val_incl
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -7,11 +7,9 @@ from sqlalchemy import or_, func, text, and_
|
|||||||
import traceback
|
import traceback
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ProductInboundService:
|
class ProductInboundService:
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 0. 辅助:唯一性校验
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_unique(serial_number, exclude_id=None):
|
def _check_unique(serial_number, exclude_id=None):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -24,9 +22,6 @@ class ProductInboundService:
|
|||||||
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料"
|
||||||
raise ValueError(f"序列号【{serial_number}】已存在!被成品 [{occupied_name}] 占用,请核查。")
|
raise ValueError(f"序列号【{serial_number}】已存在!被成品 [{occupied_name}] 占用,请核查。")
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 1. 基础物料搜索 (已修改支持分页)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword, page=1, limit=50):
|
def search_base_material(keyword, page=1, limit=50):
|
||||||
try:
|
try:
|
||||||
@ -37,7 +32,7 @@ class ProductInboundService:
|
|||||||
or_(
|
or_(
|
||||||
MaterialBase.name.ilike(kw),
|
MaterialBase.name.ilike(kw),
|
||||||
MaterialBase.spec_model.ilike(kw),
|
MaterialBase.spec_model.ilike(kw),
|
||||||
MaterialBase.company_name.ilike(kw) # [新增]
|
MaterialBase.company_name.ilike(kw)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
query = query.order_by(MaterialBase.id.desc())
|
query = query.order_by(MaterialBase.id.desc())
|
||||||
@ -46,7 +41,7 @@ class ProductInboundService:
|
|||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
results.append({
|
results.append({
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'company_name': item.company_name, # [新增]
|
'company_name': item.company_name,
|
||||||
'name': item.name,
|
'name': item.name,
|
||||||
'spec': item.spec_model,
|
'spec': item.spec_model,
|
||||||
'category': item.category,
|
'category': item.category,
|
||||||
@ -64,9 +59,6 @@ class ProductInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {"items": [], "total": 0, "page": 1, "has_next": False}
|
return {"items": [], "total": 0, "page": 1, "has_next": False}
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 1.5 BOM 搜索逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_bom_options(keyword):
|
def search_bom_options(keyword):
|
||||||
from app.models.bom import BomTable
|
from app.models.bom import BomTable
|
||||||
@ -103,9 +95,6 @@ class ProductInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 2. 新增入库逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_inbound(data):
|
def handle_inbound(data):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -137,6 +126,11 @@ class ProductInboundService:
|
|||||||
in_date_val = current_time
|
in_date_val = current_time
|
||||||
|
|
||||||
in_qty = float(data.get('in_quantity') or 0)
|
in_qty = float(data.get('in_quantity') or 0)
|
||||||
|
raw_cost = float(data.get('raw_material_cost') or 0)
|
||||||
|
manual_cost = 0.0 # 字段已弃用,保持向后兼容
|
||||||
|
unit_total_cost = float(data.get('unit_total_cost') or raw_cost or 0)
|
||||||
|
total_price = unit_total_cost * in_qty
|
||||||
|
|
||||||
p_start = data.get('production_start_time', '')
|
p_start = data.get('production_start_time', '')
|
||||||
p_end = data.get('production_end_time', '')
|
p_end = data.get('production_end_time', '')
|
||||||
time_range = f"{p_start} ~ {p_end}" if p_start or p_end else None
|
time_range = f"{p_start} ~ {p_end}" if p_start or p_end else None
|
||||||
@ -175,8 +169,8 @@ class ProductInboundService:
|
|||||||
work_order_code=data.get('work_order_code'),
|
work_order_code=data.get('work_order_code'),
|
||||||
production_manager=data.get('production_manager'),
|
production_manager=data.get('production_manager'),
|
||||||
production_time_range=time_range,
|
production_time_range=time_range,
|
||||||
raw_material_cost=float(data.get('raw_material_cost') or 0),
|
raw_material_cost=raw_cost,
|
||||||
manual_cost=float(data.get('manual_cost') or 0),
|
manual_cost=unit_total_cost,
|
||||||
quality_status=data.get('quality_status', '合格'),
|
quality_status=data.get('quality_status', '合格'),
|
||||||
product_photo=json.dumps(photo_list),
|
product_photo=json.dumps(photo_list),
|
||||||
quality_report_link=json.dumps(quality_list),
|
quality_report_link=json.dumps(quality_list),
|
||||||
@ -193,9 +187,6 @@ class ProductInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 3. 更新逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_inbound(stock_id, data):
|
def update_inbound(stock_id, data):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -230,7 +221,7 @@ class ProductInboundService:
|
|||||||
|
|
||||||
if 'sale_price' in data: stock.sale_price = float(data['sale_price'])
|
if 'sale_price' in data: stock.sale_price = float(data['sale_price'])
|
||||||
if 'raw_material_cost' in data: stock.raw_material_cost = float(data['raw_material_cost'])
|
if 'raw_material_cost' in data: stock.raw_material_cost = float(data['raw_material_cost'])
|
||||||
if 'manual_cost' in data: stock.manual_cost = float(data['manual_cost'])
|
if 'unit_total_cost' in data: stock.manual_cost = float(data['unit_total_cost']) # 映射到 manual_cost 物理字段
|
||||||
|
|
||||||
if 'in_quantity' in data:
|
if 'in_quantity' in data:
|
||||||
new_qty = float(data['in_quantity'])
|
new_qty = float(data['in_quantity'])
|
||||||
@ -239,6 +230,7 @@ class ProductInboundService:
|
|||||||
stock.stock_quantity = float(stock.stock_quantity) + diff
|
stock.stock_quantity = float(stock.stock_quantity) + diff
|
||||||
stock.available_quantity = float(stock.available_quantity) + diff
|
stock.available_quantity = float(stock.available_quantity) + diff
|
||||||
|
|
||||||
|
|
||||||
if 'production_start_time' in data or 'production_end_time' in data:
|
if 'production_start_time' in data or 'production_end_time' in data:
|
||||||
old_range = stock.production_time_range or " ~ "
|
old_range = stock.production_time_range or " ~ "
|
||||||
parts = old_range.split(' ~ ')
|
parts = old_range.split(' ~ ')
|
||||||
@ -254,9 +246,6 @@ class ProductInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 4. 删除逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_inbound(stock_id):
|
def delete_inbound(stock_id):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -270,9 +259,6 @@ class ProductInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 5. 出库历史
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_outbound_history(stock_id):
|
def get_outbound_history(stock_id):
|
||||||
try:
|
try:
|
||||||
@ -283,9 +269,6 @@ class ProductInboundService:
|
|||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 6. 获取列表
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
|
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -296,7 +279,7 @@ class ProductInboundService:
|
|||||||
query = query.filter(or_(
|
query = query.filter(or_(
|
||||||
MaterialBase.name.ilike(kw),
|
MaterialBase.name.ilike(kw),
|
||||||
MaterialBase.spec_model.ilike(kw),
|
MaterialBase.spec_model.ilike(kw),
|
||||||
MaterialBase.company_name.ilike(kw), # [新增]
|
MaterialBase.company_name.ilike(kw),
|
||||||
StockProduct.serial_number.ilike(kw),
|
StockProduct.serial_number.ilike(kw),
|
||||||
StockProduct.work_order_code.ilike(kw),
|
StockProduct.work_order_code.ilike(kw),
|
||||||
StockProduct.order_id.ilike(kw),
|
StockProduct.order_id.ilike(kw),
|
||||||
@ -307,7 +290,6 @@ class ProductInboundService:
|
|||||||
if material_type and material_type.strip():
|
if material_type and material_type.strip():
|
||||||
query = query.filter(MaterialBase.material_type == material_type.strip())
|
query = query.filter(MaterialBase.material_type == material_type.strip())
|
||||||
|
|
||||||
# [新增]
|
|
||||||
if company and company.strip():
|
if company and company.strip():
|
||||||
query = query.filter(MaterialBase.company_name == company.strip())
|
query = query.filter(MaterialBase.company_name == company.strip())
|
||||||
|
|
||||||
@ -335,7 +317,9 @@ class ProductInboundService:
|
|||||||
|
|
||||||
items = []
|
items = []
|
||||||
for item in current_items:
|
for item in current_items:
|
||||||
items.append(item.to_dict()) # 使用 Model to_dict
|
item_dict = item.to_dict()
|
||||||
|
item_dict['unit_total_cost'] = float(item.manual_cost or 0)
|
||||||
|
items.append(item_dict)
|
||||||
return {"total": pagination.total, "items": items}
|
return {"total": pagination.total, "items": items}
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -363,29 +347,20 @@ class ProductInboundService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 7. 获取筛选项
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filter_options():
|
def get_filter_options():
|
||||||
try:
|
try:
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
# 类别
|
categories = db.session.query(MaterialBase.category).filter(MaterialBase.category != None,
|
||||||
categories = db.session.query(MaterialBase.category) \
|
MaterialBase.category != '').distinct().all()
|
||||||
.filter(MaterialBase.category != None, MaterialBase.category != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_categories = sorted([r[0] for r in categories])
|
sorted_categories = sorted([r[0] for r in categories])
|
||||||
|
|
||||||
# 类型
|
types = db.session.query(MaterialBase.material_type).filter(MaterialBase.material_type != None,
|
||||||
types = db.session.query(MaterialBase.material_type) \
|
MaterialBase.material_type != '').distinct().all()
|
||||||
.filter(MaterialBase.material_type != None, MaterialBase.material_type != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_types = sorted([r[0] for r in types])
|
sorted_types = sorted([r[0] for r in types])
|
||||||
|
|
||||||
# [新增] 公司
|
companies = db.session.query(MaterialBase.company_name).filter(MaterialBase.company_name != None,
|
||||||
companies = db.session.query(MaterialBase.company_name) \
|
MaterialBase.company_name != '').distinct().all()
|
||||||
.filter(MaterialBase.company_name != None, MaterialBase.company_name != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_companies = sorted([r[0] for r in companies])
|
sorted_companies = sorted([r[0] for r in companies])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -398,9 +373,6 @@ class ProductInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {"categories": [], "types": [], "companies": []}
|
return {"categories": [], "types": [], "companies": []}
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 8. 获取历史负责人建议 (修改为全局查询)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_managers(keyword=None):
|
def get_history_managers(keyword=None):
|
||||||
from app.models.inbound.product import StockProduct
|
from app.models.inbound.product import StockProduct
|
||||||
@ -415,4 +387,55 @@ class ProductInboundService:
|
|||||||
return [r[0] for r in records if r[0]]
|
return [r[0] for r in records if r[0]]
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 9. BOM 原材料成本自动核算 (新增)
|
||||||
|
# ============================================================
|
||||||
|
@staticmethod
|
||||||
|
def calculate_bom_cost(bom_no, bom_version):
|
||||||
|
"""
|
||||||
|
根据 BOM 编号和版本计算原材料总成本
|
||||||
|
遍历 BOM 子件,使用原生 SQL 查物理表 bom_table,取每个子件在采购、半成品、成品三个表中的最高单价,乘以用量后累加
|
||||||
|
"""
|
||||||
|
from app.models.inbound.buy import StockBuy
|
||||||
|
from app.models.inbound.semi import StockSemi
|
||||||
|
from app.models.inbound.product import StockProduct
|
||||||
|
from sqlalchemy import func, text
|
||||||
|
try:
|
||||||
|
# 使用原生 SQL 精准查询 bom_table,避免模型映射错误
|
||||||
|
sql = text("""
|
||||||
|
SELECT child_id, dosage
|
||||||
|
FROM bom_table
|
||||||
|
WHERE bom_no = :bom_no AND version = :version
|
||||||
|
""")
|
||||||
|
bom_lines = db.session.execute(sql, {'bom_no': bom_no, 'version': bom_version}).fetchall()
|
||||||
|
|
||||||
|
total_cost = 0.0
|
||||||
|
for line in bom_lines:
|
||||||
|
component_base_id = line[0] # child_id
|
||||||
|
usage_qty = float(line[1] or 1.0) # dosage
|
||||||
|
|
||||||
|
# 1. 查采购表最高价 (不含税)
|
||||||
|
buy_price = db.session.query(func.max(StockBuy.pre_tax_unit_price)).filter(
|
||||||
|
StockBuy.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 2. 查半成品表最高价 (单件成本映射存在 manual_cost 里了)
|
||||||
|
semi_price = db.session.query(func.max(StockSemi.manual_cost)).filter(
|
||||||
|
StockSemi.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 3. 查成品表最高价 (同样存储在 manual_cost 字段里)
|
||||||
|
product_price = db.session.query(func.max(StockProduct.manual_cost)).filter(
|
||||||
|
StockProduct.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 4. 取三个表中的最大值,乘以用量 (dosage)
|
||||||
|
max_price = max(float(buy_price), float(semi_price), float(product_price))
|
||||||
|
total_cost += max_price * usage_qty
|
||||||
|
|
||||||
|
return round(total_cost, 2)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise e
|
||||||
|
|||||||
@ -7,11 +7,9 @@ from sqlalchemy import or_, func, text, and_
|
|||||||
import traceback
|
import traceback
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class SemiInboundService:
|
class SemiInboundService:
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 0. 辅助:唯一性校验
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_unique(base_id, serial_number, batch_number, exclude_id=None):
|
def _check_unique(base_id, serial_number, batch_number, exclude_id=None):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -34,9 +32,6 @@ class SemiInboundService:
|
|||||||
if query.first():
|
if query.first():
|
||||||
raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复建单,建议在原批次上追加库存。")
|
raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复建单,建议在原批次上追加库存。")
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 1. 基础物料搜索 (已修改支持分页)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_base_material(keyword, page=1, limit=50):
|
def search_base_material(keyword, page=1, limit=50):
|
||||||
try:
|
try:
|
||||||
@ -47,7 +42,7 @@ class SemiInboundService:
|
|||||||
or_(
|
or_(
|
||||||
MaterialBase.name.ilike(kw),
|
MaterialBase.name.ilike(kw),
|
||||||
MaterialBase.spec_model.ilike(kw),
|
MaterialBase.spec_model.ilike(kw),
|
||||||
MaterialBase.company_name.ilike(kw) # [新增] 支持搜公司
|
MaterialBase.company_name.ilike(kw)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
query = query.order_by(MaterialBase.id.desc())
|
query = query.order_by(MaterialBase.id.desc())
|
||||||
@ -56,7 +51,7 @@ class SemiInboundService:
|
|||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
results.append({
|
results.append({
|
||||||
'id': item.id,
|
'id': item.id,
|
||||||
'company_name': item.company_name, # [新增]
|
'company_name': item.company_name,
|
||||||
'name': item.name,
|
'name': item.name,
|
||||||
'spec': item.spec_model,
|
'spec': item.spec_model,
|
||||||
'category': item.category,
|
'category': item.category,
|
||||||
@ -64,19 +59,11 @@ class SemiInboundService:
|
|||||||
'type': item.material_type,
|
'type': item.material_type,
|
||||||
'status': '启用'
|
'status': '启用'
|
||||||
})
|
})
|
||||||
return {
|
return {"items": results, "total": pagination.total, "page": page, "has_next": pagination.has_next}
|
||||||
"items": results,
|
|
||||||
"total": pagination.total,
|
|
||||||
"page": page,
|
|
||||||
"has_next": pagination.has_next
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {"items": [], "total": 0, "page": 1, "has_next": False}
|
return {"items": [], "total": 0, "page": 1, "has_next": False}
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 1.5 BOM 搜索逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_bom_options(keyword):
|
def search_bom_options(keyword):
|
||||||
from app.models.bom import BomTable
|
from app.models.bom import BomTable
|
||||||
@ -113,9 +100,6 @@ class SemiInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 2. 新增入库逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_inbound(data):
|
def handle_inbound(data):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -172,9 +156,9 @@ class SemiInboundService:
|
|||||||
|
|
||||||
in_qty = float(data.get('in_quantity') or 0)
|
in_qty = float(data.get('in_quantity') or 0)
|
||||||
raw_cost = float(data.get('raw_material_cost') or 0)
|
raw_cost = float(data.get('raw_material_cost') or 0)
|
||||||
manual_cost = float(data.get('manual_cost') or 0)
|
# 【重要修改】:把前端的 unit_total_cost(单件成本)存入原数据库的 manual_cost 字段中
|
||||||
unit_total_cost = raw_cost + manual_cost
|
unit_cost = float(data.get('unit_total_cost') or raw_cost)
|
||||||
total_value = unit_total_cost * in_qty
|
total_value = unit_cost * in_qty
|
||||||
|
|
||||||
next_global_id = 0
|
next_global_id = 0
|
||||||
try:
|
try:
|
||||||
@ -220,7 +204,7 @@ class SemiInboundService:
|
|||||||
production_end_time=p_end,
|
production_end_time=p_end,
|
||||||
production_time_range=time_range_str,
|
production_time_range=time_range_str,
|
||||||
raw_material_cost=raw_cost,
|
raw_material_cost=raw_cost,
|
||||||
manual_cost=manual_cost,
|
manual_cost=unit_cost, # 映射到 manual_cost 物理字段
|
||||||
total_price=total_value,
|
total_price=total_value,
|
||||||
arrival_photo=json.dumps(arrival_list),
|
arrival_photo=json.dumps(arrival_list),
|
||||||
quality_report_link=json.dumps(quality_report_list),
|
quality_report_link=json.dumps(quality_report_list),
|
||||||
@ -236,9 +220,6 @@ class SemiInboundService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 3. 更新逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_inbound(stock_id, data):
|
def update_inbound(stock_id, data):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -312,7 +293,6 @@ class SemiInboundService:
|
|||||||
stock.production_time_range = raw_range
|
stock.production_time_range = raw_range
|
||||||
|
|
||||||
qty_changed = False
|
qty_changed = False
|
||||||
cost_changed = False
|
|
||||||
if 'in_quantity' in data:
|
if 'in_quantity' in data:
|
||||||
new_qty = float(data['in_quantity'])
|
new_qty = float(data['in_quantity'])
|
||||||
diff = new_qty - float(stock.in_quantity)
|
diff = new_qty - float(stock.in_quantity)
|
||||||
@ -321,15 +301,16 @@ class SemiInboundService:
|
|||||||
stock.stock_quantity = float(stock.stock_quantity) + diff
|
stock.stock_quantity = float(stock.stock_quantity) + diff
|
||||||
stock.available_quantity = float(stock.available_quantity) + diff
|
stock.available_quantity = float(stock.available_quantity) + diff
|
||||||
qty_changed = True
|
qty_changed = True
|
||||||
|
|
||||||
if 'raw_material_cost' in data:
|
if 'raw_material_cost' in data:
|
||||||
stock.raw_material_cost = float(data['raw_material_cost'])
|
stock.raw_material_cost = float(data['raw_material_cost'])
|
||||||
cost_changed = True
|
if 'unit_total_cost' in data:
|
||||||
if 'manual_cost' in data:
|
stock.manual_cost = float(data['unit_total_cost']) # 映射到 manual_cost 物理字段
|
||||||
stock.manual_cost = float(data['manual_cost'])
|
|
||||||
cost_changed = True
|
if 'unit_total_cost' in data or qty_changed:
|
||||||
if cost_changed or qty_changed:
|
qty = float(stock.in_quantity or 1)
|
||||||
unit_total = float(stock.raw_material_cost) + float(stock.manual_cost)
|
# 使用存入 manual_cost 的单价计算总价
|
||||||
stock.total_price = float(stock.in_quantity) * unit_total
|
stock.total_price = float(stock.manual_cost or 0) * qty
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return stock
|
return stock
|
||||||
@ -337,9 +318,6 @@ class SemiInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 4. 删除逻辑
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_inbound(stock_id):
|
def delete_inbound(stock_id):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -354,9 +332,6 @@ class SemiInboundService:
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 5. 出库历史
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_outbound_history(stock_id):
|
def get_outbound_history(stock_id):
|
||||||
try:
|
try:
|
||||||
@ -367,9 +342,6 @@ class SemiInboundService:
|
|||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 6. 获取列表
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
|
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -381,7 +353,7 @@ class SemiInboundService:
|
|||||||
or_(
|
or_(
|
||||||
MaterialBase.name.ilike(kw),
|
MaterialBase.name.ilike(kw),
|
||||||
MaterialBase.spec_model.ilike(kw),
|
MaterialBase.spec_model.ilike(kw),
|
||||||
MaterialBase.company_name.ilike(kw), # [新增]
|
MaterialBase.company_name.ilike(kw),
|
||||||
StockSemi.batch_number.ilike(kw),
|
StockSemi.batch_number.ilike(kw),
|
||||||
StockSemi.serial_number.ilike(kw),
|
StockSemi.serial_number.ilike(kw),
|
||||||
StockSemi.sku.ilike(kw),
|
StockSemi.sku.ilike(kw),
|
||||||
@ -394,7 +366,6 @@ class SemiInboundService:
|
|||||||
if material_type and material_type.strip():
|
if material_type and material_type.strip():
|
||||||
query = query.filter(MaterialBase.material_type == material_type.strip())
|
query = query.filter(MaterialBase.material_type == material_type.strip())
|
||||||
|
|
||||||
# [新增] 公司筛选
|
|
||||||
if company and company.strip():
|
if company and company.strip():
|
||||||
query = query.filter(MaterialBase.company_name == company.strip())
|
query = query.filter(MaterialBase.company_name == company.strip())
|
||||||
|
|
||||||
@ -411,18 +382,12 @@ class SemiInboundService:
|
|||||||
)
|
)
|
||||||
pagination = query.order_by(StockSemi.production_date.desc()).paginate(page=page, per_page=limit,
|
pagination = query.order_by(StockSemi.production_date.desc()).paginate(page=page, per_page=limit,
|
||||||
error_out=False)
|
error_out=False)
|
||||||
current_items = pagination.items
|
|
||||||
|
|
||||||
def parse_img(json_str):
|
|
||||||
if not json_str: return []
|
|
||||||
try:
|
|
||||||
return json.loads(json_str) if json_str.startswith('[') else [json_str]
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for item in current_items:
|
for item in pagination.items:
|
||||||
items.append(item.to_dict()) # 直接使用 Model 的 to_dict (已包含 company_name)
|
# 把 manual_cost 伪装成 unit_total_cost 返回给前端
|
||||||
|
item_dict = item.to_dict()
|
||||||
|
item_dict['unit_total_cost'] = float(item.manual_cost or 0)
|
||||||
|
items.append(item_dict)
|
||||||
return {"total": pagination.total, "items": items}
|
return {"total": pagination.total, "items": items}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"List Error: {e}")
|
print(f"List Error: {e}")
|
||||||
@ -451,29 +416,18 @@ class SemiInboundService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 7. 获取筛选项 (排序)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_filter_options():
|
def get_filter_options():
|
||||||
try:
|
try:
|
||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
# 类别
|
categories = db.session.query(MaterialBase.category).filter(MaterialBase.category != None,
|
||||||
categories = db.session.query(MaterialBase.category) \
|
MaterialBase.category != '').distinct().all()
|
||||||
.filter(MaterialBase.category != None, MaterialBase.category != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_categories = sorted([r[0] for r in categories])
|
sorted_categories = sorted([r[0] for r in categories])
|
||||||
|
types = db.session.query(MaterialBase.material_type).filter(MaterialBase.material_type != None,
|
||||||
# 类型
|
MaterialBase.material_type != '').distinct().all()
|
||||||
types = db.session.query(MaterialBase.material_type) \
|
|
||||||
.filter(MaterialBase.material_type != None, MaterialBase.material_type != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_types = sorted([r[0] for r in types])
|
sorted_types = sorted([r[0] for r in types])
|
||||||
|
companies = db.session.query(MaterialBase.company_name).filter(MaterialBase.company_name != None,
|
||||||
# [新增] 公司
|
MaterialBase.company_name != '').distinct().all()
|
||||||
companies = db.session.query(MaterialBase.company_name) \
|
|
||||||
.filter(MaterialBase.company_name != None, MaterialBase.company_name != '') \
|
|
||||||
.distinct().all()
|
|
||||||
sorted_companies = sorted([r[0] for r in companies])
|
sorted_companies = sorted([r[0] for r in companies])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -482,13 +436,9 @@ class SemiInboundService:
|
|||||||
"companies": sorted_companies
|
"companies": sorted_companies
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {"categories": [], "types": [], "companies": []}
|
return {"categories": [], "types": [], "companies": []}
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 8. 获取历史生产负责人 (修改为全局查询)
|
|
||||||
# ============================================================
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_managers(keyword=None):
|
def get_history_managers(keyword=None):
|
||||||
from app.models.inbound.semi import StockSemi
|
from app.models.inbound.semi import StockSemi
|
||||||
@ -503,4 +453,52 @@ class SemiInboundService:
|
|||||||
return [r[0] for r in records if r[0]]
|
return [r[0] for r in records if r[0]]
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_bom_cost(bom_no, bom_version):
|
||||||
|
"""
|
||||||
|
根据 BOM 编号和版本计算原材料总成本
|
||||||
|
遍历 BOM 子件,使用原生 SQL 查物理表 bom_table,取每个子件在采购、半成品、成品三个表中的最高单价,乘以用量后累加
|
||||||
|
"""
|
||||||
|
from app.models.inbound.buy import StockBuy
|
||||||
|
from app.models.inbound.semi import StockSemi
|
||||||
|
from app.models.inbound.product import StockProduct
|
||||||
|
from sqlalchemy import func, text
|
||||||
|
try:
|
||||||
|
# 使用原生 SQL 精准查询 bom_table,避免模型映射错误
|
||||||
|
sql = text("""
|
||||||
|
SELECT child_id, dosage
|
||||||
|
FROM bom_table
|
||||||
|
WHERE bom_no = :bom_no AND version = :version
|
||||||
|
""")
|
||||||
|
bom_lines = db.session.execute(sql, {'bom_no': bom_no, 'version': bom_version}).fetchall()
|
||||||
|
|
||||||
|
total_cost = 0.0
|
||||||
|
for line in bom_lines:
|
||||||
|
component_base_id = line[0] # child_id
|
||||||
|
usage_qty = float(line[1] or 1.0) # dosage
|
||||||
|
|
||||||
|
# 1. 查采购表最高价 (不含税)
|
||||||
|
buy_price = db.session.query(func.max(StockBuy.pre_tax_unit_price)).filter(
|
||||||
|
StockBuy.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 2. 查半成品表最高价 (单件成本映射存在 manual_cost 里了)
|
||||||
|
semi_price = db.session.query(func.max(StockSemi.manual_cost)).filter(
|
||||||
|
StockSemi.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 3. 查成品表最高价 (同样存储在 manual_cost 字段里)
|
||||||
|
product_price = db.session.query(func.max(StockProduct.manual_cost)).filter(
|
||||||
|
StockProduct.base_id == component_base_id
|
||||||
|
).scalar() or 0.0
|
||||||
|
|
||||||
|
# 4. 取三个表中的最大值,乘以用量 (dosage)
|
||||||
|
max_price = max(float(buy_price), float(semi_price), float(product_price))
|
||||||
|
total_cost += max_price * usage_qty
|
||||||
|
|
||||||
|
return round(total_cost, 2)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise e
|
||||||
|
|||||||
@ -156,7 +156,7 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default="scope" v-else-if="['sale_price', 'raw_material_cost', 'manual_cost'].includes(col.prop)">
|
<template #default="scope" v-else-if="['sale_price', 'raw_material_cost', 'unit_total_cost', 'total_price'].includes(col.prop)">
|
||||||
<span class="money-text">{{ formatMoney(scope.row[col.prop]) }}</span>
|
<span class="money-text">{{ formatMoney(scope.row[col.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -392,14 +392,21 @@
|
|||||||
<el-date-picker v-model="form.production_time_range" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
<el-date-picker v-model="form.production_time_range" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="8">
|
||||||
<el-form-item label="原料成本">
|
<el-form-item label="原料成本">
|
||||||
<el-input-number v-model="form.raw_material_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
<el-input-number v-model="form.raw_material_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="8">
|
||||||
<el-form-item label="人工成本">
|
<el-form-item label="单件成本">
|
||||||
<el-input-number v-model="form.manual_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
<el-input-number v-model="form.unit_total_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="总成本">
|
||||||
|
<el-input-number v-model="form.total_price" :precision="2" :controls="false" style="width:100%" placeholder="自动计算" disabled/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -467,7 +474,8 @@ import {
|
|||||||
searchMaterialBase,
|
searchMaterialBase,
|
||||||
searchBom,
|
searchBom,
|
||||||
getFilterOptions,
|
getFilterOptions,
|
||||||
getManagerHistory // [新增]
|
getManagerHistory, // [新增]
|
||||||
|
calculateBomCost
|
||||||
} from '@/api/inbound/product'
|
} from '@/api/inbound/product'
|
||||||
import { uploadFile, deleteFile } from '@/api/inbound/buy'
|
import { uploadFile, deleteFile } from '@/api/inbound/buy'
|
||||||
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
||||||
@ -561,7 +569,8 @@ const allColumns = [
|
|||||||
{ prop: 'bom_code', label: 'BOM', minWidth: '100' },
|
{ prop: 'bom_code', label: 'BOM', minWidth: '100' },
|
||||||
{ prop: 'production_manager', label: '负责人', minWidth: '100' },
|
{ prop: 'production_manager', label: '负责人', minWidth: '100' },
|
||||||
{ prop: 'raw_material_cost', label: '原料成本', minWidth: '100' },
|
{ prop: 'raw_material_cost', label: '原料成本', minWidth: '100' },
|
||||||
{ prop: 'manual_cost', label: '人工成本', minWidth: '100' },
|
{ prop: 'unit_total_cost', label: '单件成本', minWidth: '100' },
|
||||||
|
{ prop: 'total_price', label: '总成本', minWidth: '100' },
|
||||||
{ prop: 'inbound_date', label: '生产日期', minWidth: '120' },
|
{ prop: 'inbound_date', label: '生产日期', minWidth: '120' },
|
||||||
{ prop: 'detail_link', label: '详情', minWidth: '100' }
|
{ prop: 'detail_link', label: '详情', minWidth: '100' }
|
||||||
]
|
]
|
||||||
@ -586,7 +595,8 @@ const permissionMap: Record<string, string> = {
|
|||||||
bom_code: 'inbound_product:bom_code',
|
bom_code: 'inbound_product:bom_code',
|
||||||
production_manager: 'inbound_product:production_manager',
|
production_manager: 'inbound_product:production_manager',
|
||||||
raw_material_cost: 'inbound_product:raw_material_cost',
|
raw_material_cost: 'inbound_product:raw_material_cost',
|
||||||
manual_cost: 'inbound_product:manual_cost',
|
unit_total_cost: 'inbound_product:unit_total_cost',
|
||||||
|
total_price: 'inbound_product:total_price',
|
||||||
inbound_date: 'inbound_product:inbound_date',
|
inbound_date: 'inbound_product:inbound_date',
|
||||||
detail_link: 'inbound_product:detail_link',
|
detail_link: 'inbound_product:detail_link',
|
||||||
}
|
}
|
||||||
@ -642,7 +652,8 @@ const form = reactive({
|
|||||||
bom_code: '', bom_version: '', work_order_code: '', order_id: '',
|
bom_code: '', bom_version: '', work_order_code: '', order_id: '',
|
||||||
production_manager: '', production_time_range: [] as string[],
|
production_manager: '', production_time_range: [] as string[],
|
||||||
raw_material_cost: undefined as number | undefined,
|
raw_material_cost: undefined as number | undefined,
|
||||||
manual_cost: undefined as number | undefined,
|
unit_total_cost: undefined as number | undefined,
|
||||||
|
total_price: undefined as number | undefined,
|
||||||
sale_price: undefined as number | undefined,
|
sale_price: undefined as number | undefined,
|
||||||
quality_report_link: [] as string[], inspection_report_link: [] as string[], product_photo: [] as string[], detail_link: ''
|
quality_report_link: [] as string[], inspection_report_link: [] as string[], product_photo: [] as string[], detail_link: ''
|
||||||
})
|
})
|
||||||
@ -657,7 +668,7 @@ const handleSearchBom = async (query: string) => {
|
|||||||
bomOptions.value = res.data || []
|
bomOptions.value = res.data || []
|
||||||
} finally { bomSearchLoading.value = false }
|
} finally { bomSearchLoading.value = false }
|
||||||
}
|
}
|
||||||
const handleBomSelect = (val: string) => {
|
const handleBomSelect = async (val: string) => {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
form.bom_code = ''
|
form.bom_code = ''
|
||||||
form.bom_version = ''
|
form.bom_version = ''
|
||||||
@ -666,6 +677,17 @@ const handleBomSelect = (val: string) => {
|
|||||||
const [code, version] = val.split('###')
|
const [code, version] = val.split('###')
|
||||||
form.bom_code = code
|
form.bom_code = code
|
||||||
form.bom_version = version
|
form.bom_version = version
|
||||||
|
// 自动计算 BOM 成本并填入 raw_material_cost 和 unit_total_cost
|
||||||
|
try {
|
||||||
|
const res: any = await calculateBomCost({ bom_code: code, bom_version: version })
|
||||||
|
if (res.code === 200 && typeof res.data === 'number') {
|
||||||
|
form.raw_material_cost = res.data
|
||||||
|
form.unit_total_cost = res.data
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 计算失败不影响现有输入
|
||||||
|
console.warn('BOM 成本计算失败', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -860,9 +882,13 @@ const handleUpdate = (row: any) => {
|
|||||||
inspection_report_link: row.inspection_report_link || [],
|
inspection_report_link: row.inspection_report_link || [],
|
||||||
in_quantity: Number(row.qty_inbound),
|
in_quantity: Number(row.qty_inbound),
|
||||||
raw_material_cost: (row.raw_material_cost !== null && row.raw_material_cost !== undefined) ? Number(row.raw_material_cost) : undefined,
|
raw_material_cost: (row.raw_material_cost !== null && row.raw_material_cost !== undefined) ? Number(row.raw_material_cost) : undefined,
|
||||||
manual_cost: (row.manual_cost !== null && row.manual_cost !== undefined) ? Number(row.manual_cost) : undefined,
|
unit_total_cost: (row.unit_total_cost !== null && row.unit_total_cost !== undefined) ? Number(row.unit_total_cost) : undefined,
|
||||||
sale_price: (row.sale_price !== null && row.sale_price !== undefined) ? Number(row.sale_price) : undefined
|
sale_price: (row.sale_price !== null && row.sale_price !== undefined) ? Number(row.sale_price) : undefined
|
||||||
})
|
})
|
||||||
|
// 计算总成本
|
||||||
|
const u = Number(form.unit_total_cost || 0)
|
||||||
|
const q = Number(form.in_quantity || 1)
|
||||||
|
form.total_price = Number((u * q).toFixed(2))
|
||||||
if(row.production_start_time && row.production_end_time) { form.production_time_range = [row.production_start_time, row.production_end_time] } else { form.production_time_range = [] }
|
if(row.production_start_time && row.production_end_time) { form.production_time_range = [row.production_start_time, row.production_end_time] } else { form.production_time_range = [] }
|
||||||
productPhotoList.value = form.product_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
productPhotoList.value = form.product_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
||||||
const qReports = form.quality_report_link || []
|
const qReports = form.quality_report_link || []
|
||||||
@ -970,7 +996,8 @@ const submitForm = async () => {
|
|||||||
quality_report_link: qImages,
|
quality_report_link: qImages,
|
||||||
inspection_report_link: iImages,
|
inspection_report_link: iImages,
|
||||||
raw_material_cost: Number(form.raw_material_cost || 0),
|
raw_material_cost: Number(form.raw_material_cost || 0),
|
||||||
manual_cost: Number(form.manual_cost || 0),
|
unit_total_cost: Number(form.unit_total_cost || 0),
|
||||||
|
total_price: Number(form.total_price || 0),
|
||||||
sale_price: Number(form.sale_price || 0),
|
sale_price: Number(form.sale_price || 0),
|
||||||
production_start_time: form.production_time_range?.[0],
|
production_start_time: form.production_time_range?.[0],
|
||||||
production_end_time: form.production_time_range?.[1]
|
production_end_time: form.production_time_range?.[1]
|
||||||
@ -1000,7 +1027,7 @@ const handlePrint = async (row: any) => {
|
|||||||
const confirmPrint = async () => { printing.value = true; try { await executePrint(currentPrintData.value); ElMessage.success('已发送'); printVisible.value = false } catch (e: any) { ElMessage.error('打印失败') } finally { printing.value = false } }
|
const confirmPrint = async () => { printing.value = true; try { await executePrint(currentPrintData.value); ElMessage.success('已发送'); printVisible.value = false } catch (e: any) { ElMessage.error('打印失败') } finally { printing.value = false } }
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
materialOptions.value = []; bomOptions.value = []; productPhotoList.value = []; qualityFileList.value = []; inspectionFileList.value = []; quality_url.value = ''; inspection_url.value = ''
|
materialOptions.value = []; bomOptions.value = []; productPhotoList.value = []; qualityFileList.value = []; inspectionFileList.value = []; quality_url.value = ''; inspection_url.value = ''
|
||||||
Object.assign(form, { id: undefined, base_id: undefined, material_name: '', spec_model: '', material_type: '', category: '', unit: '', sku: '', barcode: '', serial_number: '', in_date: '', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', status: '在库', quality_status: '合格', bom_code: '', bom_version: '', work_order_code: '', order_id: '', production_manager: '', production_time_range: [], raw_material_cost: undefined, manual_cost: undefined, sale_price: undefined, quality_report_link: [], inspection_report_link: [], product_photo: [], detail_link: '' })
|
Object.assign(form, { id: undefined, base_id: undefined, material_name: '', spec_model: '', material_type: '', category: '', unit: '', sku: '', barcode: '', serial_number: '', in_date: '', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', status: '在库', quality_status: '合格', bom_code: '', bom_version: '', work_order_code: '', order_id: '', production_manager: '', production_time_range: [], raw_material_cost: undefined, unit_total_cost: undefined, total_price: undefined, sale_price: undefined, quality_report_link: [], inspection_report_link: [], product_photo: [], detail_link: '' })
|
||||||
}
|
}
|
||||||
const getStatusType = (s:string) => ({'在库':'success','出库':'info','借库':'warning','损耗':'danger'}[s]||'warning')
|
const getStatusType = (s:string) => ({'在库':'success','出库':'info','借库':'warning','损耗':'danger'}[s]||'warning')
|
||||||
const getQualityType = (s:string) => ({'合格':'success','不合格':'danger','待检':'info'}[s]||'info')
|
const getQualityType = (s:string) => ({'合格':'success','不合格':'danger','待检':'info'}[s]||'info')
|
||||||
@ -1011,6 +1038,13 @@ onMounted(() => {
|
|||||||
fetchData()
|
fetchData()
|
||||||
fetchOptions()
|
fetchOptions()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 成本计算监听
|
||||||
|
watch([() => form.unit_total_cost, () => form.in_quantity], ([unit, qty]) => {
|
||||||
|
const unitNum = Number(unit || 0)
|
||||||
|
const qtyNum = Number(qty || 1)
|
||||||
|
form.total_price = Number((unitNum * qtyNum).toFixed(2))
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -1087,4 +1121,4 @@ onMounted(() => {
|
|||||||
.product-dropdown { width: 580px !important; }
|
.product-dropdown { width: 580px !important; }
|
||||||
.product-dropdown .el-select-dropdown__wrap { max-height: 320px !important; }
|
.product-dropdown .el-select-dropdown__wrap { max-height: 320px !important; }
|
||||||
.product-dropdown .el-input__suffix { z-index: 10; }
|
.product-dropdown .el-input__suffix { z-index: 10; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -182,7 +182,7 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default="scope" v-else-if="['raw_material_cost', 'manual_cost', 'unit_total_cost'].includes(col.prop)">
|
<template #default="scope" v-else-if="['raw_material_cost', 'unit_total_cost', 'total_price'].includes(col.prop)">
|
||||||
<span class="money-text">{{ formatMoney(scope.row[col.prop]) }}</span>
|
<span class="money-text">{{ formatMoney(scope.row[col.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -473,13 +473,13 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="单件总成本">
|
<el-form-item label="单件成本">
|
||||||
<el-input-number v-model="form.manual_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
<el-input-number v-model="form.unit_total_cost" :precision="2" :controls="false" style="width:100%" placeholder="请输入"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="总成本">
|
<el-form-item label="总成本">
|
||||||
<el-input-number v-model="form.unit_total_cost" :precision="2" :controls="false" style="width:100%" placeholder="自动计算" disabled/>
|
<el-input-number v-model="form.total_price" :precision="2" :controls="false" style="width:100%" placeholder="自动计算" disabled/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -539,7 +539,8 @@ import {
|
|||||||
searchMaterialBase,
|
searchMaterialBase,
|
||||||
searchBom,
|
searchBom,
|
||||||
getFilterOptions,
|
getFilterOptions,
|
||||||
getManagerHistory // [新增]
|
getManagerHistory, // [新增]
|
||||||
|
calculateBomCost
|
||||||
} from '@/api/inbound/semi'
|
} from '@/api/inbound/semi'
|
||||||
import { uploadFile, deleteFile } from '@/api/inbound/buy'
|
import { uploadFile, deleteFile } from '@/api/inbound/buy'
|
||||||
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
||||||
@ -642,8 +643,8 @@ const stockColumns = [
|
|||||||
{prop: 'bom_version', label: 'BOM版本', minWidth: '90'},
|
{prop: 'bom_version', label: 'BOM版本', minWidth: '90'},
|
||||||
{prop: 'work_order_code', label: '工单号', minWidth: '120'},
|
{prop: 'work_order_code', label: '工单号', minWidth: '120'},
|
||||||
{prop: 'raw_material_cost', label: '原料成本', minWidth: '100'},
|
{prop: 'raw_material_cost', label: '原料成本', minWidth: '100'},
|
||||||
{prop: 'manual_cost', label: '单件成本', minWidth: '100'}, // 原人工成本,现为单件成本
|
{prop: 'unit_total_cost', label: '单件成本', minWidth: '100'},
|
||||||
{prop: 'unit_total_cost', label: '总成本', minWidth: '100'}, // 原单件总本,现为总成本
|
{prop: 'total_price', label: '总成本', minWidth: '100'},
|
||||||
{prop: 'production_manager', label: '生产负责人', minWidth: '100'},
|
{prop: 'production_manager', label: '生产负责人', minWidth: '100'},
|
||||||
{prop: 'production_start_time', label: '生产开始', minWidth: '160'},
|
{prop: 'production_start_time', label: '生产开始', minWidth: '160'},
|
||||||
{prop: 'production_end_time', label: '生产结束', minWidth: '160'},
|
{prop: 'production_end_time', label: '生产结束', minWidth: '160'},
|
||||||
@ -677,8 +678,8 @@ const permissionMap: Record<string, string> = {
|
|||||||
bom_version: 'inbound_semi:bom_version',
|
bom_version: 'inbound_semi:bom_version',
|
||||||
work_order_code: 'inbound_semi:work_order_code',
|
work_order_code: 'inbound_semi:work_order_code',
|
||||||
raw_material_cost: 'inbound_semi:raw_material_cost',
|
raw_material_cost: 'inbound_semi:raw_material_cost',
|
||||||
manual_cost: 'inbound_semi:manual_cost',
|
|
||||||
unit_total_cost: 'inbound_semi:unit_total_cost',
|
unit_total_cost: 'inbound_semi:unit_total_cost',
|
||||||
|
total_price: 'inbound_semi:total_price',
|
||||||
production_manager: 'inbound_semi:production_manager',
|
production_manager: 'inbound_semi:production_manager',
|
||||||
production_start_time: 'inbound_semi:production_start_time',
|
production_start_time: 'inbound_semi:production_start_time',
|
||||||
production_end_time: 'inbound_semi:production_end_time',
|
production_end_time: 'inbound_semi:production_end_time',
|
||||||
@ -726,18 +727,16 @@ const form = reactive({
|
|||||||
company_name: '',
|
company_name: '',
|
||||||
material_name: '', spec_model: '', category: '', unit: '', material_type: '', sku: '', barcode: '', in_date: '', serial_number: '', batch_number: '', status: '在库', quality_status: '合格', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', bom_code: '', bom_version: '', work_order_code: '',
|
material_name: '', spec_model: '', category: '', unit: '', material_type: '', sku: '', barcode: '', in_date: '', serial_number: '', batch_number: '', status: '在库', quality_status: '合格', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', bom_code: '', bom_version: '', work_order_code: '',
|
||||||
raw_material_cost: undefined as number | undefined,
|
raw_material_cost: undefined as number | undefined,
|
||||||
manual_cost: undefined as number | undefined,
|
|
||||||
unit_total_cost: undefined as number | undefined,
|
unit_total_cost: undefined as number | undefined,
|
||||||
|
total_price: undefined as number | undefined,
|
||||||
production_manager: '', production_time_range: [] as string[], arrival_photo: [] as string[], quality_report_link: [] as string[], detail_link: ''
|
production_manager: '', production_time_range: [] as string[], arrival_photo: [] as string[], quality_report_link: [] as string[], detail_link: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// === 新增:监听计算总成本 ===
|
// === 监听计算总成本 ===
|
||||||
watch([() => form.in_quantity, () => form.manual_cost], ([qty, manual_cost]) => {
|
watch([() => form.unit_total_cost, () => form.in_quantity], ([unit, qty]) => {
|
||||||
if (manual_cost !== undefined && manual_cost !== null) {
|
const unitNum = Number(unit || 0)
|
||||||
form.unit_total_cost = Number((qty * manual_cost).toFixed(2))
|
const qtyNum = Number(qty || 1)
|
||||||
} else {
|
form.total_price = Number((unitNum * qtyNum).toFixed(2))
|
||||||
form.unit_total_cost = undefined
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -750,7 +749,7 @@ const handleSearchBom = async (query: string) => {
|
|||||||
bomOptions.value = res.data || []
|
bomOptions.value = res.data || []
|
||||||
} finally { bomSearchLoading.value = false }
|
} finally { bomSearchLoading.value = false }
|
||||||
}
|
}
|
||||||
const handleBomSelect = (val: string) => {
|
const handleBomSelect = async (val: string) => {
|
||||||
// val 格式为 bom_no###version
|
// val 格式为 bom_no###version
|
||||||
if (!val) {
|
if (!val) {
|
||||||
form.bom_code = ''
|
form.bom_code = ''
|
||||||
@ -760,6 +759,17 @@ const handleBomSelect = (val: string) => {
|
|||||||
const [code, version] = val.split('###')
|
const [code, version] = val.split('###')
|
||||||
form.bom_code = code
|
form.bom_code = code
|
||||||
form.bom_version = version
|
form.bom_version = version
|
||||||
|
// 自动计算 BOM 成本并填入 raw_material_cost 和 unit_total_cost
|
||||||
|
try {
|
||||||
|
const res: any = await calculateBomCost({ bom_code: code, bom_version: version })
|
||||||
|
if (res.code === 200 && typeof res.data === 'number') {
|
||||||
|
form.raw_material_cost = res.data
|
||||||
|
form.unit_total_cost = res.data
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 计算失败不影响现有输入
|
||||||
|
console.warn('BOM 成本计算失败', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -989,13 +999,16 @@ const handleUpdate = (row: any) => {
|
|||||||
in_quantity: Number(row.qty_inbound), stock_quantity: Number(row.qty_stock), available_quantity: Number(row.qty_available),
|
in_quantity: Number(row.qty_inbound), stock_quantity: Number(row.qty_stock), available_quantity: Number(row.qty_available),
|
||||||
bom_code: row.bom_code, bom_version: row.bom_version, work_order_code: row.work_order_code,
|
bom_code: row.bom_code, bom_version: row.bom_version, work_order_code: row.work_order_code,
|
||||||
raw_material_cost: (row.raw_material_cost !== null && row.raw_material_cost !== undefined) ? Number(row.raw_material_cost) : undefined,
|
raw_material_cost: (row.raw_material_cost !== null && row.raw_material_cost !== undefined) ? Number(row.raw_material_cost) : undefined,
|
||||||
manual_cost: (row.manual_cost !== null && row.manual_cost !== undefined) ? Number(row.manual_cost) : undefined,
|
|
||||||
unit_total_cost: (row.unit_total_cost !== null && row.unit_total_cost !== undefined) ? Number(row.unit_total_cost) : undefined,
|
unit_total_cost: (row.unit_total_cost !== null && row.unit_total_cost !== undefined) ? Number(row.unit_total_cost) : undefined,
|
||||||
production_manager: row.production_manager,
|
production_manager: row.production_manager,
|
||||||
production_time_range: (row.production_start_time && row.production_end_time) ? [row.production_start_time, row.production_end_time] : [],
|
production_time_range: (row.production_start_time && row.production_end_time) ? [row.production_start_time, row.production_end_time] : [],
|
||||||
detail_link: row.detail_link,
|
detail_link: row.detail_link,
|
||||||
arrival_photo: row.arrival_photo || [], quality_report_link: row.quality_report_link || []
|
arrival_photo: row.arrival_photo || [], quality_report_link: row.quality_report_link || []
|
||||||
})
|
})
|
||||||
|
// 计算总成本
|
||||||
|
const u = Number(form.unit_total_cost || 0)
|
||||||
|
const q = Number(form.in_quantity || 1)
|
||||||
|
form.total_price = Number((u * q).toFixed(2))
|
||||||
arrivalFileList.value = form.arrival_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
arrivalFileList.value = form.arrival_photo.map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
||||||
const reports = form.quality_report_link || []
|
const reports = form.quality_report_link || []
|
||||||
const reportImgs = reports.filter(r => !isExternalLink(r))
|
const reportImgs = reports.filter(r => !isExternalLink(r))
|
||||||
@ -1093,8 +1106,8 @@ const submitForm = async () => {
|
|||||||
quality_report_link: onlyImages,
|
quality_report_link: onlyImages,
|
||||||
in_quantity: Number(form.in_quantity),
|
in_quantity: Number(form.in_quantity),
|
||||||
raw_material_cost: Number(form.raw_material_cost || 0),
|
raw_material_cost: Number(form.raw_material_cost || 0),
|
||||||
manual_cost: Number(form.manual_cost || 0),
|
|
||||||
unit_total_cost: Number(form.unit_total_cost || 0),
|
unit_total_cost: Number(form.unit_total_cost || 0),
|
||||||
|
total_price: Number(form.total_price || 0),
|
||||||
production_start_time: form.production_time_range?.[0] || null,
|
production_start_time: form.production_time_range?.[0] || null,
|
||||||
production_end_time: form.production_time_range?.[1] || null
|
production_end_time: form.production_time_range?.[1] || null
|
||||||
}
|
}
|
||||||
@ -1131,7 +1144,7 @@ const resetForm = () => {
|
|||||||
id: undefined, base_id: undefined,
|
id: undefined, base_id: undefined,
|
||||||
company_name: '', // [新增]
|
company_name: '', // [新增]
|
||||||
material_name: '', spec_model: '', category: '', unit: '', material_type: '', sku: '', barcode: '', in_date: '', serial_number: '', batch_number: '', status: '在库', quality_status: '合格', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', bom_code: '', bom_version: '', work_order_code: '',
|
material_name: '', spec_model: '', category: '', unit: '', material_type: '', sku: '', barcode: '', in_date: '', serial_number: '', batch_number: '', status: '在库', quality_status: '合格', in_quantity: 1, stock_quantity: 1, available_quantity: 1, warehouse_location: '', bom_code: '', bom_version: '', work_order_code: '',
|
||||||
raw_material_cost: undefined, manual_cost: undefined, unit_total_cost: undefined,
|
raw_material_cost: undefined, unit_total_cost: undefined, total_price: undefined,
|
||||||
production_manager: '', production_time_range: [], arrival_photo: [], quality_report_link: [], detail_link: '' })
|
production_manager: '', production_time_range: [], arrival_photo: [], quality_report_link: [], detail_link: '' })
|
||||||
}
|
}
|
||||||
const getStatusType = (status: string) => { const map: any = { '在库': 'success', '出库': 'info', '借库': 'warning', '损耗': 'danger' }; return map[status] || 'warning' }
|
const getStatusType = (status: string) => { const map: any = { '在库': 'success', '出库': 'info', '借库': 'warning', '损耗': 'danger' }; return map[status] || 'warning' }
|
||||||
@ -1221,4 +1234,4 @@ onMounted(() => {
|
|||||||
.long-dropdown { width: 580px !important; }
|
.long-dropdown { width: 580px !important; }
|
||||||
.long-dropdown .el-select-dropdown__wrap { max-height: 320px !important; }
|
.long-dropdown .el-select-dropdown__wrap { max-height: 320px !important; }
|
||||||
.long-dropdown .el-input__suffix { z-index: 10; }
|
.long-dropdown .el-input__suffix { z-index: 10; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user