639 lines
24 KiB
Python
639 lines
24 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import BigInteger, create_engine, select
|
|
from sqlalchemy.ext.compiler import compiles
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
|
|
@compiles(BigInteger, "sqlite")
|
|
def _compile_bigint_for_sqlite(type_, compiler, **kw) -> str:
|
|
_ = type_, compiler, kw
|
|
return "INTEGER"
|
|
|
|
|
|
import app.models.master_data # noqa: E402,F401
|
|
import app.models.miniapp # noqa: E402,F401
|
|
import app.models.operations # noqa: E402,F401
|
|
import app.models.org # noqa: E402,F401
|
|
import app.models.planning # noqa: E402,F401
|
|
import app.models.sales # noqa: E402,F401
|
|
from app.api.routes.purchase import create_purchase_order, create_purchase_receipt, lock_purchase_order # noqa: E402
|
|
from app.models.base import Base # noqa: E402
|
|
from app.models.master_data import Bom, BomItem, Item, Material, Product, StockBalance, Warehouse # noqa: E402
|
|
from app.models.operations import ( # noqa: E402
|
|
InventoryTxn,
|
|
ProcessRoute,
|
|
PurchaseOrderItem,
|
|
PurchaseReceiptItem,
|
|
StockLot,
|
|
Supplier,
|
|
WorkOrder,
|
|
WorkOrderMaterial,
|
|
WorkOrderMaterialIssue,
|
|
WorkOrderOperation,
|
|
)
|
|
from app.models.org import Department, Employee, User # noqa: E402
|
|
from app.schemas.operations import PurchaseOrderCreate, PurchaseOrderItemCreate, PurchaseReceiptCreate, PurchaseReceiptItemCreate # noqa: E402
|
|
from app.services.auth import AuthContext # noqa: E402
|
|
from app.services.operations import issue_item_from_lots, sync_work_order_reference_qty # noqa: E402
|
|
|
|
|
|
class RawMaterialWeightOnlyFlowTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
|
Base.metadata.create_all(engine)
|
|
self.SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
|
|
self.db: Session = self.SessionLocal()
|
|
self._seed_master_data()
|
|
|
|
def tearDown(self) -> None:
|
|
self.db.close()
|
|
|
|
def _seed_master_data(self) -> None:
|
|
self.department = Department(id=1, dept_code="D-WH", dept_name="仓库部", dept_type="WAREHOUSE", status="ACTIVE")
|
|
self.employee = Employee(id=1, employee_code="EMP-WH-001", employee_name="仓管员", dept_id=1, status="ACTIVE")
|
|
self.user = User(
|
|
id=1,
|
|
username="tester",
|
|
password_hash="x",
|
|
employee_id=1,
|
|
dept_id=1,
|
|
nickname="测试员",
|
|
is_super_admin=1,
|
|
status="ACTIVE",
|
|
)
|
|
self.material_item = Item(
|
|
id=1,
|
|
item_code="RM-WEIGHT-001",
|
|
item_name="重量口径原材料",
|
|
item_type="RAW_MATERIAL",
|
|
unit_weight_kg=Decimal("1.000000"),
|
|
status="ACTIVE",
|
|
)
|
|
self.material = Material(
|
|
item_id=1,
|
|
default_supplier_id=1,
|
|
purchase_calc_mode="BY_WEIGHT",
|
|
min_purchase_qty=Decimal("0"),
|
|
purchase_multiple_qty=Decimal("0"),
|
|
purchase_multiple_weight_kg=Decimal("0"),
|
|
lead_time_days=0,
|
|
)
|
|
self.supplier = Supplier(
|
|
id=1,
|
|
supplier_code="SUP-WEIGHT-001",
|
|
supplier_name="重量供应商",
|
|
short_name="重量供应商",
|
|
lead_time_days=0,
|
|
default_tax_rate=Decimal("0"),
|
|
status="ACTIVE",
|
|
)
|
|
self.warehouse = Warehouse(id=1, warehouse_code="WH-RM", warehouse_name="原材料库", warehouse_type="RAW", status="ACTIVE")
|
|
self.db.add_all([
|
|
self.department,
|
|
self.employee,
|
|
self.user,
|
|
self.material_item,
|
|
self.material,
|
|
self.supplier,
|
|
self.warehouse,
|
|
])
|
|
self.db.commit()
|
|
|
|
def _auth_context(self) -> AuthContext:
|
|
return AuthContext(
|
|
user=self.user,
|
|
employee=self.employee,
|
|
department=self.department,
|
|
role_codes=["ADMIN"],
|
|
role_names=["管理员"],
|
|
permission_codes=[],
|
|
)
|
|
|
|
def _create_locked_raw_purchase_order(self, *, order_weight_kg: Decimal = Decimal("200"), unit_price: Decimal = Decimal("3.5")):
|
|
order = create_purchase_order(
|
|
PurchaseOrderCreate(
|
|
supplier_id=self.supplier.id,
|
|
expected_date=date.today(),
|
|
items=[
|
|
PurchaseOrderItemCreate(
|
|
material_item_id=self.material_item.id,
|
|
order_weight_kg=order_weight_kg,
|
|
unit_price=unit_price,
|
|
)
|
|
],
|
|
),
|
|
db=self.db,
|
|
)
|
|
po_item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == order.purchase_order_id))
|
|
lock_purchase_order(order.purchase_order_id, db=self.db)
|
|
return order, po_item
|
|
|
|
def _raw_receipt_payload(
|
|
self,
|
|
order,
|
|
po_item,
|
|
*,
|
|
received_weight_kg: Decimal = Decimal("100"),
|
|
unit_cost: Decimal = Decimal("3.5"),
|
|
waybill_no: str | None = "YD-CG-001",
|
|
order_photo_url: str | None = None,
|
|
freight_amount: Decimal | None = Decimal("12.5"),
|
|
) -> PurchaseReceiptCreate:
|
|
return PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no=waybill_no,
|
|
order_photo_url=order_photo_url,
|
|
freight_amount=freight_amount,
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
received_weight_kg=received_weight_kg,
|
|
unit_cost=unit_cost,
|
|
)
|
|
],
|
|
)
|
|
|
|
def test_raw_purchase_receipt_requires_logistics_info(self) -> None:
|
|
order, po_item = self._create_locked_raw_purchase_order()
|
|
|
|
with self.assertRaises(HTTPException) as exc:
|
|
create_purchase_receipt(
|
|
self._raw_receipt_payload(order, po_item, waybill_no="", order_photo_url="", freight_amount=None),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
self.assertEqual(exc.exception.status_code, 400)
|
|
self.assertIn("运单号或上传辅助照片", exc.exception.detail)
|
|
self.assertIn("运费", exc.exception.detail)
|
|
|
|
def test_raw_purchase_receipt_allows_photo_instead_of_waybill_and_records_logistics(self) -> None:
|
|
order, po_item = self._create_locked_raw_purchase_order()
|
|
|
|
create_purchase_receipt(
|
|
self._raw_receipt_payload(
|
|
order,
|
|
po_item,
|
|
waybill_no="",
|
|
order_photo_url="/inventory/logistics-photos/purchase-in.jpg",
|
|
freight_amount=Decimal("18.5"),
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
lot = self.db.scalar(select(StockLot))
|
|
txn = self.db.scalar(select(InventoryTxn).where(InventoryTxn.txn_type == "PURCHASE_IN"))
|
|
|
|
self.assertIsNone(lot.logistics_waybill_no)
|
|
self.assertEqual(lot.logistics_photo_url, "/inventory/logistics-photos/purchase-in.jpg")
|
|
self.assertEqual(float(lot.logistics_freight_amount), 18.5)
|
|
self.assertIsNone(txn.logistics_waybill_no)
|
|
self.assertEqual(txn.logistics_photo_url, "/inventory/logistics-photos/purchase-in.jpg")
|
|
self.assertEqual(float(txn.logistics_freight_amount), 18.5)
|
|
|
|
def test_purchase_receipt_writes_raw_material_weight_only(self) -> None:
|
|
order = create_purchase_order(
|
|
PurchaseOrderCreate(
|
|
supplier_id=self.supplier.id,
|
|
expected_date=date.today(),
|
|
items=[
|
|
PurchaseOrderItemCreate(
|
|
material_item_id=self.material_item.id,
|
|
order_weight_kg=Decimal("200"),
|
|
unit_price=Decimal("3.5"),
|
|
)
|
|
],
|
|
),
|
|
db=self.db,
|
|
)
|
|
po_item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == order.purchase_order_id))
|
|
self.assertEqual(float(po_item.order_qty), 0)
|
|
self.assertEqual(float(po_item.order_weight_kg), 200)
|
|
|
|
lock_purchase_order(order.purchase_order_id, db=self.db)
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-001",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
received_weight_kg=Decimal("250"),
|
|
unit_cost=Decimal("3.5"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
self.db.refresh(po_item)
|
|
receipt_item = self.db.scalar(select(PurchaseReceiptItem))
|
|
lot = self.db.scalar(select(StockLot))
|
|
balance = self.db.scalar(select(StockBalance))
|
|
txn = self.db.scalar(select(InventoryTxn))
|
|
|
|
self.assertEqual(float(po_item.received_qty), 0)
|
|
self.assertEqual(float(po_item.received_weight_kg), 250)
|
|
self.assertEqual(float(receipt_item.received_qty), 0)
|
|
self.assertEqual(float(receipt_item.received_weight_kg), 250)
|
|
self.assertEqual(float(lot.inbound_qty), 0)
|
|
self.assertEqual(float(lot.remaining_qty), 0)
|
|
self.assertEqual(float(lot.locked_qty), 0)
|
|
self.assertEqual(float(lot.inbound_weight_kg), 250)
|
|
self.assertEqual(float(balance.qty_on_hand), 0)
|
|
self.assertEqual(float(balance.qty_available), 0)
|
|
self.assertEqual(float(balance.weight_on_hand_kg), 250)
|
|
self.assertEqual(float(txn.qty_change), 0)
|
|
self.assertEqual(float(txn.weight_change_kg), 250)
|
|
|
|
def test_purchase_receipt_ignores_legacy_merge_payload_and_creates_new_inventory_lot(self) -> None:
|
|
order = create_purchase_order(
|
|
PurchaseOrderCreate(
|
|
supplier_id=self.supplier.id,
|
|
expected_date=date.today(),
|
|
items=[
|
|
PurchaseOrderItemCreate(
|
|
material_item_id=self.material_item.id,
|
|
order_weight_kg=Decimal("500"),
|
|
unit_price=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
db=self.db,
|
|
)
|
|
po_item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == order.purchase_order_id))
|
|
lock_purchase_order(order.purchase_order_id, db=self.db)
|
|
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-002",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
received_weight_kg=Decimal("300"),
|
|
unit_cost=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
first_lot = self.db.scalar(select(StockLot))
|
|
self.assertEqual(float(first_lot.unit_cost), 4)
|
|
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-003",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
merge_to_lot_id=first_lot.id,
|
|
received_weight_kg=Decimal("200"),
|
|
unit_cost=Decimal("99"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
self.db.refresh(first_lot)
|
|
second_lot = self.db.scalars(select(StockLot).order_by(StockLot.id)).all()[1]
|
|
receipt_items = self.db.scalars(select(PurchaseReceiptItem).order_by(PurchaseReceiptItem.id)).all()
|
|
txns = self.db.scalars(select(InventoryTxn).order_by(InventoryTxn.id)).all()
|
|
|
|
self.assertEqual(float(first_lot.inbound_weight_kg), 300)
|
|
self.assertEqual(float(first_lot.unit_cost), 4)
|
|
self.assertEqual(second_lot.lot_no, "YL0002")
|
|
self.assertEqual(float(second_lot.inbound_weight_kg), 200)
|
|
self.assertEqual(float(second_lot.unit_cost), 99)
|
|
self.assertIsNone(receipt_items[1].merge_to_lot_id)
|
|
self.assertEqual(receipt_items[1].lot_no, "YL0002")
|
|
self.assertEqual(float(receipt_items[1].unit_cost), 99)
|
|
self.assertEqual(float(txns[1].unit_cost), 99)
|
|
self.assertEqual(float(txns[1].amount), 19800)
|
|
|
|
def test_purchase_receipt_ignores_manual_legacy_lot_no(self) -> None:
|
|
order = create_purchase_order(
|
|
PurchaseOrderCreate(
|
|
supplier_id=self.supplier.id,
|
|
expected_date=date.today(),
|
|
items=[
|
|
PurchaseOrderItemCreate(
|
|
material_item_id=self.material_item.id,
|
|
order_weight_kg=Decimal("500"),
|
|
unit_price=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
db=self.db,
|
|
)
|
|
po_item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == order.purchase_order_id))
|
|
lock_purchase_order(order.purchase_order_id, db=self.db)
|
|
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-004",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
lot_no="YL-NB-20260530-RMWEIGHT-OLD-L01",
|
|
received_weight_kg=Decimal("300"),
|
|
unit_cost=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
lot = self.db.scalar(select(StockLot))
|
|
receipt_item = self.db.scalar(select(PurchaseReceiptItem))
|
|
|
|
self.assertEqual(lot.lot_no, "YL0001")
|
|
self.assertEqual(receipt_item.lot_no, "YL0001")
|
|
|
|
def test_split_arrivals_create_sequential_inventory_lots_for_same_purchase_line(self) -> None:
|
|
order = create_purchase_order(
|
|
PurchaseOrderCreate(
|
|
supplier_id=self.supplier.id,
|
|
expected_date=date.today(),
|
|
items=[
|
|
PurchaseOrderItemCreate(
|
|
material_item_id=self.material_item.id,
|
|
order_weight_kg=Decimal("500"),
|
|
unit_price=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
db=self.db,
|
|
)
|
|
po_item = self.db.scalar(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == order.purchase_order_id))
|
|
lock_purchase_order(order.purchase_order_id, db=self.db)
|
|
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-005",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
received_weight_kg=Decimal("300"),
|
|
unit_cost=Decimal("4"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
create_purchase_receipt(
|
|
PurchaseReceiptCreate(
|
|
purchase_order_id=order.purchase_order_id,
|
|
warehouse_id=self.warehouse.id,
|
|
receiver_employee_id=self.employee.id,
|
|
waybill_no="YD-CG-RAW-006",
|
|
freight_amount=Decimal("10"),
|
|
items=[
|
|
PurchaseReceiptItemCreate(
|
|
purchase_order_item_id=po_item.id,
|
|
material_item_id=self.material_item.id,
|
|
received_weight_kg=Decimal("250"),
|
|
unit_cost=Decimal("4.5"),
|
|
)
|
|
],
|
|
),
|
|
context=self._auth_context(),
|
|
db=self.db,
|
|
)
|
|
|
|
lots = self.db.scalars(select(StockLot).order_by(StockLot.id)).all()
|
|
receipt_items = self.db.scalars(select(PurchaseReceiptItem).order_by(PurchaseReceiptItem.id)).all()
|
|
self.assertEqual([lot.lot_no for lot in lots], ["YL0001", "YL0002"])
|
|
self.assertEqual(float(receipt_items[0].unit_cost), 4)
|
|
self.assertEqual(float(receipt_items[1].unit_cost), 4.5)
|
|
self.assertEqual(float(lots[0].unit_cost), 4)
|
|
self.assertEqual(float(lots[1].unit_cost), 4.5)
|
|
|
|
def test_material_issue_ignores_historical_raw_quantity_pollution(self) -> None:
|
|
lot = StockLot(
|
|
lot_no="RML-POLLUTED-001",
|
|
lot_role="INBOUND_RAW",
|
|
item_id=self.material_item.id,
|
|
warehouse_id=self.warehouse.id,
|
|
source_doc_type="TEST",
|
|
source_doc_id=1,
|
|
inbound_qty=Decimal("4000"),
|
|
inbound_weight_kg=Decimal("3000"),
|
|
remaining_qty=Decimal("4000"),
|
|
remaining_weight_kg=Decimal("3000"),
|
|
locked_qty=Decimal("0"),
|
|
locked_weight_kg=Decimal("0"),
|
|
unit_cost=Decimal("2"),
|
|
quality_status="PASS",
|
|
status="AVAILABLE",
|
|
)
|
|
balance = StockBalance(
|
|
item_id=self.material_item.id,
|
|
warehouse_id=self.warehouse.id,
|
|
qty_on_hand=Decimal("4000"),
|
|
weight_on_hand_kg=Decimal("3000"),
|
|
qty_available=Decimal("4000"),
|
|
weight_available_kg=Decimal("3000"),
|
|
qty_allocated=Decimal("0"),
|
|
weight_allocated_kg=Decimal("0"),
|
|
avg_unit_cost=Decimal("2"),
|
|
)
|
|
self.db.add_all([lot, balance])
|
|
self.db.commit()
|
|
|
|
issued_qty, issued_weight, amount = issue_item_from_lots(
|
|
self.db,
|
|
item_id=self.material_item.id,
|
|
required_qty=Decimal("999"),
|
|
required_weight=Decimal("200"),
|
|
enforce_qty=True,
|
|
enforce_weight=True,
|
|
source_doc_id=1,
|
|
source_line_id=None,
|
|
biz_time=datetime.now(),
|
|
operator_user_id=self.user.id,
|
|
remark="测试按重量领料",
|
|
)
|
|
self.db.commit()
|
|
|
|
self.db.refresh(lot)
|
|
self.db.refresh(balance)
|
|
txn = self.db.scalar(select(InventoryTxn))
|
|
|
|
self.assertEqual(float(issued_qty), 0)
|
|
self.assertEqual(float(issued_weight), 200)
|
|
self.assertEqual(float(amount), 400)
|
|
self.assertEqual(float(lot.remaining_qty), 0)
|
|
self.assertEqual(float(lot.remaining_weight_kg), 2800)
|
|
self.assertEqual(float(balance.qty_on_hand), 0)
|
|
self.assertEqual(float(balance.qty_available), 0)
|
|
self.assertEqual(float(balance.weight_on_hand_kg), 2800)
|
|
self.assertEqual(float(txn.qty_change), 0)
|
|
self.assertEqual(float(txn.weight_change_kg), -200)
|
|
|
|
def test_work_order_reference_qty_resyncs_from_issued_weight_and_bom_gross_weight(self) -> None:
|
|
product_item = Item(
|
|
id=2,
|
|
item_code="FG-WEIGHT-001",
|
|
item_name="重量口径成品",
|
|
item_type="FINISHED_GOOD",
|
|
unit_weight_kg=Decimal("1.000000"),
|
|
status="ACTIVE",
|
|
)
|
|
product = Product(
|
|
item_id=2,
|
|
version_no="V1.0",
|
|
standard_output_qty=Decimal("0"),
|
|
allowed_scrap_rate=Decimal("0"),
|
|
)
|
|
bom = Bom(
|
|
id=1,
|
|
bom_code="BOM-WEIGHT-001",
|
|
product_item_id=2,
|
|
version_no="V1.0",
|
|
is_default=1,
|
|
status="ACTIVE",
|
|
)
|
|
bom_item = BomItem(
|
|
id=1,
|
|
bom_id=1,
|
|
seq_no=1,
|
|
material_item_id=self.material_item.id,
|
|
usage_type="WEIGHT",
|
|
qty_per_piece=Decimal("1"),
|
|
net_weight_per_piece_kg=Decimal("1.000000"),
|
|
gross_weight_per_piece_kg=Decimal("1.500000"),
|
|
loss_rate=Decimal("0.3333"),
|
|
)
|
|
route = ProcessRoute(
|
|
id=1,
|
|
route_code="ROUTE-WEIGHT-001",
|
|
route_name="重量测试路线",
|
|
product_item_id=2,
|
|
version_no="V1.0",
|
|
is_default=1,
|
|
status="ACTIVE",
|
|
)
|
|
work_order = WorkOrder(
|
|
id=1,
|
|
work_order_no="WO-STale-001",
|
|
work_order_type="NORMAL",
|
|
product_item_id=2,
|
|
route_id=1,
|
|
bom_id=1,
|
|
planned_qty=Decimal("52"),
|
|
released_qty=Decimal("52"),
|
|
finished_qty=Decimal("0"),
|
|
scrap_qty=Decimal("0"),
|
|
status="RELEASED",
|
|
)
|
|
material_row = WorkOrderMaterial(
|
|
id=1,
|
|
work_order_id=1,
|
|
bom_item_id=1,
|
|
material_item_id=self.material_item.id,
|
|
required_qty=Decimal("0"),
|
|
required_weight_kg=Decimal("200"),
|
|
issued_qty=Decimal("0"),
|
|
issued_weight_kg=Decimal("200"),
|
|
returned_qty=Decimal("0"),
|
|
returned_weight_kg=Decimal("0"),
|
|
status="ISSUED",
|
|
)
|
|
issue_row = WorkOrderMaterialIssue(
|
|
id=1,
|
|
work_order_material_id=1,
|
|
work_order_id=1,
|
|
material_item_id=self.material_item.id,
|
|
source_lot_id=1,
|
|
material_sub_batch_no="JH_RM-WEIGHT-001_0001",
|
|
issued_qty=Decimal("0"),
|
|
issued_weight_kg=Decimal("200"),
|
|
unit_cost=Decimal("2"),
|
|
amount=Decimal("400"),
|
|
issue_time=datetime.now(),
|
|
)
|
|
operation = WorkOrderOperation(
|
|
id=1,
|
|
work_order_id=1,
|
|
route_operation_id=1,
|
|
seq_no=1,
|
|
operation_name="1序",
|
|
work_center_id=1,
|
|
planned_qty=Decimal("52"),
|
|
report_qty=Decimal("0"),
|
|
good_qty=Decimal("0"),
|
|
scrap_qty=Decimal("0"),
|
|
rework_qty=Decimal("0"),
|
|
status="PENDING",
|
|
)
|
|
source_lot = StockLot(
|
|
id=1,
|
|
lot_no="RML-ISSUE-001",
|
|
lot_role="INBOUND_RAW",
|
|
item_id=self.material_item.id,
|
|
warehouse_id=self.warehouse.id,
|
|
source_doc_type="TEST",
|
|
source_doc_id=1,
|
|
inbound_weight_kg=Decimal("200"),
|
|
remaining_weight_kg=Decimal("0"),
|
|
unit_cost=Decimal("2"),
|
|
quality_status="PASS",
|
|
status="DEPLETED",
|
|
)
|
|
self.db.add_all([product_item, product, bom, bom_item, route, work_order, material_row, source_lot, issue_row, operation])
|
|
self.db.commit()
|
|
|
|
changed = sync_work_order_reference_qty(self.db, work_order.id)
|
|
self.db.commit()
|
|
|
|
self.db.refresh(work_order)
|
|
self.db.refresh(operation)
|
|
self.assertTrue(changed)
|
|
self.assertEqual(float(work_order.planned_qty), 133)
|
|
self.assertEqual(float(work_order.released_qty), 133)
|
|
self.assertEqual(float(operation.planned_qty), 133)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|