물류팀의 카테고리는 총 두가지로
1. 재고관리
2. 입출고관리
가 있다.
물류팀인만큼 물류팀의 주요 기능은 상품 / 제품의 관리를 하는 것이 주요한 기능이다.
우리는 우선 상품테이블인 erp_goods 테이블과 상품 등급, 분류, 종류, 규격 그리고 제일 중요한 로트로 나누어 따로 관리할 수 있게 만들었다. (유지 보수의 용이성을 위해)
상품 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goods | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goods_no | sequence | INT | O | ||||||
goods_code | 상품코드 | VARCHAR | 30 | NOT NULL | UNIQUE | 자동완성입력 | |||
goods_barcode | 바코드 | VARCHAR | 30 | NOT NULL | 입력 | ||||
goods_name | 상품명 | VARCHAR | 100 | NOT NULL | |||||
goodskind_no | 종류테이블 seq | INT | O |
마우스/노트북/스피커/키보드 등등
|
|||||
goods_customerprice | 소비자가 | INT | 부가세포함 | ||||||
goods_description | 상품설명 | VARCHAR | 500 | ||||||
goodsst_no | 규격테이블 seq | INT | O | ||||||
client_no1 | 거래처 테이블 seq | INT | O | 제조사 | |||||
client_no2 | 거래처 테이블 seq | INT | O | 책임판매업자 | |||||
goods_stockqty | 재고수량 | INT | |||||||
goodslev_no | 상품 재고 등급 테이블 seq | INT | O | 상품재고등급 | |||||
goods_location | 재고위치 | VARCHAR | 50 | ||||||
comcode_no | 회사코드(고객사) 테이블 seq | INT | O | 회사코드 부여 | |||||
상품-분류 테이블 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goodssort | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goodssort_no | sequence | INT | O | ||||||
goodssort_code | 상품 분류코드 | VARCHAR | 30 | NOT NULL | UNIQUE | 자동완성 해서 입력 | |||
goodssort_name | 상품 분류명 | VARCHAR | 30 | NOT NULL |
상품/제품/반제품/원자재/부자재
|
||||
상품-종류 테이블 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goodskind | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goodskind_no | sequence | INT | O | ||||||
goodssort_no | 분류테이블 seq | INT | O | 분류 | |||||
goodskind_code | 상품 종류코드 | VARCHAR | 30 | NOT NULL | UNIQUE | 자동완성입력 | |||
goodskind_name | 상품 종류명 | VARCHAR | 100 | NOT NULL |
마우스/노트북/스피커/키보드 등등
|
||||
상품-규격 테이블 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goodsst | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goodsst_no | sequence | INT | O | ||||||
goodsst_unit | 단위 | VARCHAR | 30 | ||||||
goodsst_spec | 상품 사양 | VARCHAR | 300 |
ex) cpu : 인텔i5 ....., 재질 등
|
|||||
goodsst_size | 상품 사이즈 | VARCHAR | 300 | 100x100x100mm | |||||
goodsst_package | 포장 사이즈 | VARCHAR | 300 | ||||||
goodsst_ea | 상품 사입량 | INT | 한 박스당 몇개 | ||||||
상품-재고등급 테이블 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goodslev | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goodslev_no | sequence | INT | O | ||||||
goodslev_grade | 재고등급 | VARCHAR | 30 | NOT NULL | UNIQUE |
정상, 단상자파손, 스크래치, 전시상품, 파손
|
|||
goodslev_description | 재고등급 설명 | VARCHAR | 300 | ||||||
상품-로트번호 테이블 | 사용 카테고리 | ||||||||
공동 테이블명 | erp_goodslot | ||||||||
Column | Description | Data Type | Length | Null | Initial Value | Primary Key | Foreign Key | Constraint | Remark |
칼럼명 | 설명 | 데이터 타입 | 길이 | 널(null)값 | 초기값 | 기본키 | 외래키 | 제약조건 | 비고 |
goodslot_no | sequence | INT | O | ||||||
goodslot_lot | 로트번호 | VARCHAR | 30 | ||||||
goodslot_qty | 수량 | INT | |||||||
goodslot_production | 제조일자 | DATE | |||||||
goodslot_expiry | 유통기한 | DATE | |||||||
goodslot_price | 원가 | INT | |||||||
goodslot_tax | 세액 | INT | |||||||
goodslot_total | 총 원가 | INT | |||||||
goods_no | 상품 테이블 seq | INT | O |
재고관리에서는 기본적인 CRUD를 비롯하여, 수량만 변경을 한다거나 같은 상품이지만 로트번호는 다를 수 있는 경우를 신경써야 하기 때문에 생각보단 기능구현에 있어서 어려움을 겪었다.
우선, insert를 할 때 같은 상품이지만 로트번호는 다른 경우가 있기 때문에 로트추가 버튼을 누르면 새로운 행이 생기도록 했다.
이때 이용한 JS코드는
let lotIndex = 1; // 로트 정보 행 인덱스
const maxLotCount = 3; // 최대 로트 정보 개수
function addLotRow() {
if (lotIndex < maxLotCount) {
lotIndex++;
const table = document.getElementById("lotTable");
const row1 = table.insertRow(table.rows.length); // 첫 번째 행 추가
const row2 = table.insertRow(table.rows.length); // 두 번째 행 추가
// 첫 번째 로트 정보 행 추가
row1.innerHTML = `
<th>로트번호</th>
<td><input type="text" id="goodslot_lot"` + lotIndex + ` name="goodslot_lot${lotIndex}"></td>
<th>로트별수량</th>
<td><input type="number" id="goodslot_qty${lotIndex}" name="goodslot_qty${lotIndex}"></td>
<th>제조일자</th>
<td><input type="date" id="goodslot_production${lotIndex}" name="goodslot_production${lotIndex}"></td>
`;
// 두 번째 로트 정보 행 추가
row2.innerHTML = `
<th>세액</th>
<td><input type="number" id="goodslot_tax${lotIndex}" name="goodslot_tax${lotIndex}"></td>
<th>원가</th>
<td><input type="number" id="goodslot_price${lotIndex}" name="goodslot_price${lotIndex}"></td>
<th>유통기한</th>
<td><input type="date" id="goodslot_expiry${lotIndex}" name="goodslot_expiry${lotIndex}"></td>
`;
} else {
alert("최대 3개의 로트 정보까지만 추가 가능합니다.");
}
}
function removeLotRow() {
if (lotIndex > 1) {
const table = document.getElementById("lotTable");
table.deleteRow(table.rows.length - 1);
table.deleteRow(table.rows.length - 1);
lotIndex--;
} else {
alert("최소한 한 개의 로트 정보가 필요합니다.");
}
}
//등록 버튼 클릭 시 실행되는 함수
function validateAndSubmit() {
const goodsCode = document.getElementById("goods_code").value;
const goodsBarcode = document.getElementById("goods_barcode").value;
const goodsName = document.getElementById("goods_name").value;
const goodsStockqty = document.getElementById("goods_stockqty").value;
const goodsstUnit = document.getElementById("goodsst_unit").value;
const goodsstSpec = document.getElementById("goodsst_spec").value;
const goodsstSize = document.getElementById("goodsst_size").value;
const goodsstPackage = document.getElementById("goodsst_package").value;
const goodsstEa = document.getElementById("goodsst_ea").value;
const goodsCustomerprice = document.getElementById("goods_customerprice").value;
console.log(goodsCode);
console.log(goodsBarcode);
if(goodsCode == ""){
alert("상품코드를 입력해 주세요.");
document.getElementById('goods_code').focus();
return false;
}else if(goodsBarcode == ""){
alert("바코드를 입력해 주세요.");
document.getElementById('goods_barcode').focus();
return false;
}else if(goodsName == ""){
alert("상품명을 입력해 주세요.");
document.getElementById('goods_name').focus();
return false;
}else if(goodsCustomerprice == ""){
alert("소비자가를 입력해 주세요.");
document.getElementById("goods_customerprice").focus();
return false;
}else if(goodsStockqty <= 0){
alert("재고수량을 입력해 주세요.")
document.getElementById('goods_stockqty').focus();
return false;
}else if(goodsstUnit == ""){
alert("단위를 입력해 주세요.");
document.getElementById('goodsst_unit').focus();
return false;
}else if(goodsstSpec == ""){
alert("상품사양을 입력해 주세요.");
document.getElementById('goodsst_spec').focus();
return false;
}else if(goodsstSize == ""){
alert("상품사이즈를 입력해 주세요.");
document.getElementById('goodsst_size').focus();
return false;
}else if(goodsstPackage == ""){
alert("포장사이즈를 입력해 주세요.");
document.getElementById('goodsst_package').focus();
return false;
}else if(goodsstEa == ""){
alert("상품 사입량을 입력해 주세요.");
document.getElementById('goodsst_ea').focus();
return false
}
let totalLotQty = 0;
const lotQtyInput = document.getElementsByName("goodslot_qty");
const goodslotProductionInput = document.getElementsByName("goodslot_production");
const goodslotExpiryInput = document.getElementsByName("goodslot_expiry");
var sum = 0;
for (var h = 0; h < lotQtyInput.length; h++) {
sum = Number(sum) + Number(lotQtyInput[h].value);
}
const stockQtyInput = document.getElementById("goods_stockqty");
const stockQtyValue = parseInt(stockQtyInput.value) || 0;
if (sum !== stockQtyValue) {
alert("재고수량과 로트별수량의 합이 일치해야 합니다.");
if (lotIndex >= 1) {
const firstLotQtyInput = document.getElementById(`goodslot_qty1`);
firstLotQtyInput.focus();
}
return false;
}
// 제조일자와 유통기한 비교
for (var i = 0; i < lotQtyInput.length; i++) {
const productionDate = new Date(goodslotProductionInput[i].value);
const expiryDate = new Date(goodslotExpiryInput[i].value);
if (expiryDate < productionDate) {
alert("유통기한이 제조일자보다 빠릅니다. 유통기한을 다시 입력해 주세요.");
goodslotExpiryInput[i].focus();
return false;
}
}
document.getElementById("create").submit();
}
addLotRow 함수는 "로트 정보 추가" 버튼을 클릭했을 때 실행된다.
먼저, 현재 로트 정보의 개수인 lotIndex를 확인하고 최대 로트 정보 개수인 maxLotCount보다 작을 때만 로트 정보를 추가할 수 있다. lotIndex를 증가시키고, 테이블에서 두 개의 새로운 행을 추가하고 각 행은 로트 정보를 입력하는 데 사용된다.
첫 번째 행에는 로트 번호, 로트별 수량, 제조일자의 입력 필드가 생성되고, 두 번째 행에는 세액, 원가, 유통기한의 입력 필드가 생성된다.
(이때 추가되는 행에 대해서는 name값에 lotIndex의 값을 붙여 숫자를 추가했는데 이것은 추후에 Controller에 대한 설명을 할 때 마저 이야기하겠다.)
removeLotRow 함수는 "로트 정보 삭제" 버튼을 클릭했을 때 실행된다.
현재 추가된 로트 정보의 개수인 lotIndex를 확인하고, 최소한 한 개의 로트 정보가 남아있을 때만 로트 정보를 삭제할 수 있고, 테이블에서 마지막 두 행(로트 정보 행)을 삭제하고 lotIndex를 감소시킨다.
validateAndSubmit 함수는 "등록" 버튼을 클릭했을 때 실행되며, 입력된 데이터를 유효성 검사하고 제출하는 역할을 한다.
먼저, 상품 정보와 관련된 입력 필드의 내용을 변수에 저장한다. (ex. goodsCode, goodsBarcode, goodsName 등)
각 입력 필드에 대한 유효성 검사를 수행한다. 예를 들어, 상품코드, 바코드, 상품명, 소비자가, 재고 수량 등이 비어 있거나 유효하지 않으면 경고 메시지를 표시하고 해당 필드에 포커스를 준다.
로트 정보의 총 수량(sum)을 계산하고, 이 값이 상품의 재고 수량과 일치하는지 확인한다. 일치하지 않으면 경고 메시지를 표시하고 첫 번째 로트 정보의 수량 입력 필드에 포커스를 준다.
각 로트의 제조일자와 유통기한을 비교하여 유효성 검사를 하고, 유통기한이 제조일자보다 빠를 경우 경고 메시지를 표시하고 해당 유통기한 입력 필드에 포커스를 준다.
모든 유효성 검사를 통과하면 폼 데이터를 제출한다.
'ERP Project' 카테고리의 다른 글
물류팀 기능구현(2) (0) | 2023.10.12 |
---|---|
재무팀 기능구현(2) (0) | 2023.10.10 |
재무팀 기능구현(1) (0) | 2023.09.20 |
인사팀 기능구현(2) (0) | 2023.09.20 |
인사팀 기능구현(1) (0) | 2023.09.20 |