接收入庫的種類接收入庫可以按照資料來源分為2種:
1. 對於po訂單以及發放的接收入庫;
2. 對於內部請購單生成的sales order(後面的部分簡稱so)的接收入庫;
接收入庫還可以按照入庫方式分為3種:
1. 直接入庫:接收後自動入庫;
2. 標準入庫:先接收,後入庫,分兩個步驟;
3. 需要檢驗的接收入庫:先接收,然後檢驗,最後入庫;
對於這些不同型別的入庫,程式的差別可能會很大。所以一個完整靈活可以應付所有接收入庫的程式也是較為複雜的。
資料流動 我們將分析接收入庫的資料來源,針對不同的接收入庫類別,給出最常見實用的取數邏輯。最後我們將給出一個總體的資料流圖,其中包含了各個資料點間的層次關係,幫助大家理解。
其中涉及到的較為重要的表(我們忽略了一些顯而易見的表,比如po_headers_all,po_line_locations_all等等):
mtl_supply
|
最重要的一張表,其中包括了所有可以接收入庫的記錄
|
Rcv_transaction
|
這張表的內容是已經接收入庫的事物處理記錄,在標準入庫或檢驗入庫的時候,我們可能需要其中的接收資訊。
|
Rcv_shipment_headers
|
發運頭表
|
Rcv_shipment_lines
|
發執行表
|
標準訂單以及一攬子採購協議發放1. 對於標準訂單,當訂單被approve之後,系統會在mtl_supply這張表中插入該訂單相關的記錄,系統預設為每一個訂單分配行生成一條mtl_supply資料,其supply_type_code值為“PO”,表示物料供應的來源,目前是來自po訂單。所以mtl_supply表中,對於標準訂單以及一攬子發放,其資料層次是明細到分配行的,即根據distribution_id可以唯一確定一個mtl_supply行:
SELECT pha.segment1,
pla.line_num,
plla.shipment_num,
pda.distribution_num,
ms.supply_type_code,
ms.quantity
FROM mtl_supply ms,
po_headers_all pha,
po_lines_all pla,
po_line_locations_all plla,
po_distributions_all pda
WHERE ms.po_distribution_id = pda.po_distribution_id
AND ms.supply_type_code = 'PO'
AND pha.po_header_id = pla.po_header_id
AND plla.po_header_id = pha.po_header_id
AND plla.po_line_id = pla.po_line_id
AND pda.line_location_id = plla.line_location_id
AND pha.segment1 = '1000100'
以上sql語句將篩選出訂單編號為‘1000100’的訂單可以接收的distribution行以及可以接收的數量。
注意:在這個時候,rcv_shipmet_headers以及rcv_shipment_lines這兩張表中還沒有資料,這是po訂單與內部請購單接收資料流中的一個較大的差異。
造成這種差異的原因是:
對於po訂單,其發運物件是外部供應商,系統無法控制以及檢測外部供應商的發運動作,所以只能在做接收的時候,才能生成發運資訊,在任何接收之前,系統是不知道任何發運資料的;
而對於內部請購單生成的Sales Order來說,發運物件是公司內部其它組織或ou,發運資訊是在其他組織或OU做SO的發運時就產生了,而不必等到接收的時候才產生相應的發運資訊。
由於以上差異,在po訂單做接收時,介面中不必提供shipment資訊,而在po入庫以及內部請購單生成的so接收的時候,則需要提供shipment資訊。
2. 接下來,當我們為po訂單做接收的時候,系統會生成相應的shipment資訊,經過測試,每次接收會生成一個新的shipment頭,每個接收行會生成一個新的shipment行。所以,對於po接收,其接收的transaction id與發執行的shipment line id是一一對應的,在做po入庫的時候,可以利用這一點,透過shipment line 找到可以入庫的parent transaction id。另外,對於po接收,其在發執行中的quantity received與quantity shipped欄位的值總是一樣的,因為發運記錄是完全參照接收記錄生成的。
3. 對於直接入庫的訂單,接收後將同時在RCV_TRANSACTION中生成接收記錄以及入庫記錄;
對於標準接收則分為接收和入庫兩個步驟,接收生成接收事務處理記錄,並且同時在MTL_SUPPLY中可以找到mtl_supply_type為“RECEIVING”的記錄,表示存在可入庫記錄了,入庫來源是“RECEIVING”。入庫後再同一張表(RCV_TRANSACTION)中生成入庫事務處理記錄;雖然接收後在MTL_SUPPLY表中可以查到可入庫的資訊,但是因為接收後已經在RCV_TRANSACTION中產生了接收事務處理記錄,理論上來說,以上兩者的資料應當是等價的,但是這時候我們習慣於從RCV_TRANSACTION表中查詢可以入庫的記錄。
對於需要檢驗的接收入庫,在接收後,需要完成檢驗,對接收記錄打上檢驗透過標記後,才能進行入庫動作,最終生成入庫事務處理記錄。
標準訂單接收入庫資料流向圖
對內部請購單生成的Sales Order的接收入庫內部請購單因為涉及到內部銷售訂單以及發運資訊,所以比PO單的接收多了若干環節,顯得較為複雜。基本的資料流如下:
1. 建立內部請購單,審批成功後,透過請求生成對應的Sales Order,完成從PO表到OE表的第一個步驟的資料流動。可以透過OE表的Reference欄位來追溯對應的請購單(IR)。這時候雖然在MTL_SUPPLY中也產生了供應資料,但是這個時候是無法對這個IR進行接收的,因為對應的SO還沒有發運記錄,沒有發運怎麼能接收呢。
2. 對SO進行挑庫發放,確認發運後,在 RCV_SHIPMENT中生成了發運記錄。完成了第二個步驟的資料流動。注意,由於存在SO拆行,以及分批發運的狀況,所以發執行與IR的行是不存在嚴格意義上的數量對應關係的,一個IR行生成的SO行,可能被拆分在若干個發運記錄中,這也是在做IR接收入庫時,介面表要求插入發運資訊的原因,對於IR的接收,是以發運事務處理為單位的。發放確認後,在MTL_SUPPLY中,將可以找到supply_type_code為“SHIPMENT”的供應記錄,表示從這個時刻起,這個IR相關的訂單可以開始接收了,接收來源是“SHIPMENT”;
3. 接下來的步驟基本和PO接收入庫一致。值得注意的是在對IR生成的SO做接收時,可以發現一個明顯的特點,接收事務處理介面中的可接收記錄根據發運方式被拆成了多行,而不像PO接收時那樣一個PO行對應一行可接收記錄。這也直接說明了對於IR來說接收是以發運為單位進行的。
介面表注意事項不同的入庫方式Oracle提供了三種可選的入庫方式(在PO訂單分配行介面維護):
1. 直接入庫:接收後自動入庫;
2. 標準入庫:接收後,進行入庫動作,才完成最終入庫;
3. 要求檢驗的入庫:接收後,進行檢驗,列印檢驗透過標記後,才能進行入庫動作。
對於這三種接收入庫,介面表透過不同的控制欄位來完成期望的接收入庫動作:
a) 對於直接入庫的接收,插接收事務處理的介面行表(rcv_transactions_interface)時的幾個關鍵的狀態欄位:
rcv_transactions_interface.transaction_type := 'RECEIVE';
rcv_transactions_interface.auto_transact_code := 'DELIVER';
rcv_transactions_interface.destination_type_code := 'INVENTORY';
b) 對於標準以及需檢驗的入庫的接收,插接收事務處理的介面行表(rcv_transactions_interface)時的幾個關鍵的狀態欄位:
rcv_transactions_interface.transaction_type := 'RECEIVE';
rcv_transactions_interface.auto_transact_code := NULL;
rcv_transactions_interface.destination_type_code := ' RECEIVING ';
c) 對於接收甚至檢驗後的資料做入庫時,插接收事務處理的介面行表(rcv_transactions_interface)時的幾個關鍵的狀態欄位:
rcv_transactions_interface.transaction_type := 'DELIVER';
rcv_transactions_interface.auto_transact_code := NULL;
rcv_transactions_interface.destination_type_code := 'INVENTORY';
可入庫數量我們需要判斷可入庫數量,這是對入庫介面資料的一項基本校驗,是否有足夠的數量可以入庫,我們分別給出PO以及IR的可入庫數量的演算法:
對於PO單(標準採購訂單,一攬子發放):
FUNCTION check_po_valid_quantity(p_routing_id IN NUMBER
,p_line_location_id IN NUMBER
,p_deliver_qty IN NUMBER
,p_uom_code IN VARCHAR2
,p_item_id IN NUMBER) RETURN VARCHAR2 IS
l_qty NUMBER := 0;
l_valid_quantity NUMBER := 0;
l_ship_uom_code VARCHAR2(3);
CURSOR cur_valid_lines(i_routing_id IN NUMBER) IS
SELECT ms.quantity, muom.uom_code
FROM mtl_supply ms, rcv_transactions rt, mtl_units_of_measure muom
WHERE rt.transaction_id = ms.rcv_transaction_id
AND rt.inspection_status_code =
decode(i_routing_id, 1, rt.inspection_status_code, 2, 'ACCEPTED')
AND ms.supply_type_code = 'RECEIVING'
AND ms.po_line_location_id = p_line_location_id
AND muom.unit_of_measure = ms.unit_of_measure;
BEGIN
IF (p_routing_id = '1' OR p_routing_id = '2') THEN
--standard or inspect required deliver
FOR rec_valid_line IN cur_valid_lines(p_routing_id) LOOP
l_qty := rec_valid_line.quantity;
--transact quantity according to uom
IF (rec_valid_line.uom_code <> p_uom_code) THEN
l_qty := inv_convert.inv_um_convert(p_item_id,
6,
l_qty,
rec_valid_line.uom_code,
p_uom_code,
NULL,
NULL);
END IF;
l_valid_quantity := l_valid_quantity + l_qty;
END LOOP;
ELSIF (p_routing_id = '3') THEN
-- direct deliver
SELECT plla.quantity - plla.quantity_received - plla.quantity_cancelled, muom.uom_code
INTO l_valid_quantity, l_ship_uom_code
FROM po_line_locations_all plla, mtl_units_of_measure muom
WHERE plla.quantity - plla.quantity_received - plla.quantity_cancelled > 0
AND muom.unit_of_measure = plla.unit_meas_lookup_code
AND plla.line_location_id = p_line_location_id;
--transact quantity according to uom
IF (l_ship_uom_code <> p_uom_code) THEN
l_valid_quantity := inv_convert.inv_um_convert(p_item_id,
6,
l_valid_quantity,
l_ship_uom_code,
p_uom_code,
NULL,
NULL);
END IF;
END IF;
xxdomi_cn_conc_utl.debug('l_valid_quantity : ' || l_valid_quantity || ' ;p_deliver_qty : ' ||
p_deliver_qty);
IF (l_valid_quantity >= p_deliver_qty) THEN
RETURN 'VALID';
ELSE
RETURN 'INVALID';
END IF;
EXCEPTION
WHEN OTHERS THEN
xxdomi_cn_conc_utl.log_msg('check_po_valid_quantity: ' || SQLERRM);
RETURN 'INVALID';
END check_po_valid_quantity;
對於IR FUNCTION check_ir_valid_quantity(p_routing_id IN NUMBER
,p_req_line_id IN NUMBER
,p_deliver_qty IN NUMBER
,p_uom_code IN VARCHAR2
,p_item_id IN NUMBER) RETURN VARCHAR2 IS
l_qty NUMBER := 0;
l_valid_quantity NUMBER := 0;
--valid lines for routing_id 1&2
CURSOR cur_valid_lines(i_routing_id IN NUMBER) IS
SELECT ms.quantity, muom.uom_code
FROM mtl_supply ms, rcv_transactions rt, mtl_units_of_measure muom
WHERE ms.req_line_id = p_req_line_id
AND ms.supply_type_code = 'RECEIVING'
AND ms.rcv_transaction_id = rt.transaction_id
AND rt.inspection_status_code =
decode(i_routing_id, 1, rt.inspection_status_code, 2, 'ACCEPTED')
AND muom.unit_of_measure = ms.unit_of_measure;
--valid lines for routing_id 3
CURSOR cur_dir_valid_lines IS
SELECT ms.quantity, muom.uom_code
FROM mtl_supply ms, mtl_units_of_measure muom
WHERE ms.req_line_id = p_req_line_id
AND ms.supply_type_code = 'SHIPMENT'
AND muom.unit_of_measure = ms.unit_of_measure;
BEGIN
IF (p_routing_id = '1' OR p_routing_id = '2') THEN
--standard or inspect required deliver
FOR rec_valid_line IN cur_valid_lines(p_routing_id) LOOP
l_qty := rec_valid_line.quantity;
--transact quantity according to uom
IF (rec_valid_line.uom_code <> p_uom_code) THEN
l_qty := inv_convert.inv_um_convert(p_item_id,
6,
l_qty,
rec_valid_line.uom_code,
p_uom_code,
NULL,
NULL);
END IF;
l_valid_quantity := l_valid_quantity + l_qty;
END LOOP;
ELSIF (p_routing_id = '3') THEN
--direct deliver
FOR rec_dir_valid_line IN cur_dir_valid_lines LOOP
l_qty := rec_dir_valid_line.quantity;
--transact quantity according to uom
IF (rec_dir_valid_line.uom_code <> p_uom_code) THEN
l_qty := inv_convert.inv_um_convert(p_item_id,
6,
l_qty,
rec_dir_valid_line.uom_code,
p_uom_code,
NULL,
NULL);
END IF;
l_valid_quantity := l_valid_quantity + l_qty;
END LOOP;
END IF;
IF (l_valid_quantity >= p_deliver_qty) THEN
RETURN 'VALID';
ELSE
RETURN 'INVALID';
END IF;
EXCEPTION
WHEN OTHERS THEN
xxdomi_cn_conc_utl.log_msg('check_ir_valid_quantity: ' || SQLERRM);
RETURN 'INVALID';
END check_ir_valid_quantity;
以上檢查程式綜合考慮了不同的三種入庫方式,其中p_routing_id有三種值:
1:表示標準入庫;
2:表示需要檢驗的入庫;
3:表示直接入庫;
以上程式也考慮到了單位的轉換,支援入庫單位與接收單位不想同的情況下的入庫數量校驗。
對於帶批次或序列號的接收入庫 對於帶批次或序列號的接收入庫,對於1行rcv_transactions_interface記錄需要額外插兩張MTL的介面表。這裡有一個比較詭異的地方,就是接收入庫時,插rcv_serials_interface,以及rcv_lots_interface這兩張表是沒用的,而一定要插 mtl_transaction_lots_interface以及mtl_serial_numbers_interface這兩張MTL表,而且對應的要設定
rcv_transactions_interface.use_mtl_lot := 2;
rcv_transactions_interface.use_mtl_serial := 2;
以下程式在11i下驗證透過。
--lot info------------
IF (rec_line.lot_number IS NOT NULL ) THEN
mtl_transaction_lots_interfac.last_update_date := SYSDATE ;
mtl_transaction_lots_interfac.last_updated_by := fnd_global.user_id;
mtl_transaction_lots_interfac.creation_date := SYSDATE ;
mtl_transaction_lots_interfac.created_by := fnd_global.user_id;
mtl_transaction_lots_interfac.last_update_login := - 1 ;
mtl_transaction_lots_interfac.product_code := 'RCV' ;
mtl_transaction_lots_interfac.product_transaction_id := rcv_transactions_interface.interface_transaction_id;
mtl_transaction_lots_interfac.lot_number := rec_line.lot_number;
mtl_transaction_lots_interfac.transaction_quantity := l_iface_rcv_rec.quantity;
mtl_transaction_lots_interfac.primary_quantity := l_primary_qty;
SELECT mtl_material_transactions_s.NEXTVAL
INTO mtl_transaction_lots_interfac.transaction_interface_id
FROM dual;
l_primary_qty :=1;
INSERT INTO mtl_transaction_lots_interface VALUES mtl_transaction_lots_interfac;
END IF ;
--serial info
IF (serial_number IS NOT NULL ) THEN
mtl_serial_numbers_interface.last_update_date := SYSDATE ;
mtl_serial_numbers_interface.last_updated_by := fnd_global.user_id;
mtl_serial_numbers_interface.creation_date := SYSDATE ;
mtl_serial_numbers_interface.created_by := fnd_global.user_id;
mtl_serial_numbers_interface.last_update_login := - 1 ;
mtl_serial_numbers_interface.product_code := 'RCV' ;
mtl_serial_numbers_interface.fm_serial_number :=serial_number;
mtl_serial_numbers_interface.to_serial_number :=serial_number;
mtl_serial_numbers_interface.process_flag := 1 ;
mtl_serial_numbers_interface.product_transaction_id := l_iface_rcv_rec.interface_transaction_id;
SELECT mtl_material_transactions_s.NEXTVAL
INTO mtl_serial_numbers_interface.transaction_interface_id
FROM dual;
INSERT INTO mtl_serial_numbers_interface VALUES mtl_serial_numbers_interface;
END IF;