1. VBA란 무엇인가?

  : 엑셀에 복잡한 프로그래밍(Visual Basic기반)을 적용하고자 할 때 사용합니다. 대신 이 VBA가 적용되어있는 엑셀은 

   다른이름으로 저장해서 확장자를 xlsm (통합매크로확장자)로 저장해야됩니다. (이전에는 바로 저장해도 사용가능했지만..)


2. 장점

  - 아주 복잡한 프로그램을 간단하고 가볍게 만든다. 

  - 수천,수십만 라인에서 관련된 수식을 잘못건드리면 연관된 수식이 작동해서 컴퓨터가 뻗어버린다.(자동계산시)

    VBA는 프로그래밍 코드만 저장되면 되므로 엑셀 內에 따로 수식이 들어갈 필요가 없다.

  - 수십만 라인이 있을 경우 일반적으로 가로 합계만 계산한다고 해도 수십만 컬럼에 동일한 수식이 들어가 있게 된다.

    이렇게 되면 수식Text를 모두 저장해야 가능하기에 파일용량이 매우 커진다.

     그러나 VBA를 사용하면 수식을 일일이 컬럼에 넣을필요가 없어 파일 용량이 작아진다.


3. VBA 추가기능 설치하기

   - 엑셀의 파일메뉴 > 옵션 > 팝업화면에서 추가기능 > 오른쪽하단 Excel추가기능 선택 후 "이동"버튼 클릭

     > 추가기능 창에서 "분석도구 -VBA" 선택 > 확인클릭

   - 이렇게 하면 엑셀 메뉴 상단에 "개발도구"메뉴가 생기는데 VBA를 편집할 수 있는 tool을 이용할 수 있다.


다음시간에는 나만의 Excel함수를 만들어 보도록 하겠습니다.



만족하셨나요? ~~~~~~~

1. 배열함수

때로는 SUM만 가지고 해결될 수 없는 조건이 들어가 있는 합계를 원할때가 있다.

이럴때 배열함수라는 것을 사용하는데 한 함수에서 여러 범위를 지정하고 조건을 달아놓으면

해당 조건에 해당하는 건만 필터링되어 결과물을 얻을 수 있다.


2. 예시

 : 아래 테이블에서 원하는 두가지 조건에 해당하는 건들의 출장비 합을 구해보도록 하겠다.

  

 이름 

 성별 

출장비

 김모 

  

 30,000

 박모 

  

 80,000

 김모 

  

 60,000

 강모 

  

 70,000

 황모 

  

 80,000

 김모 

  

 90,000


 - 이름이 "김모"씨고 성별이 "여"인 사람의 출장비 합계를 구하라.

1) 먼저 임의의 비어있는 컬럼에 SUM함수를 사용하여 SUM함수 내에서 조건을 단다.   

=SUM((B4:B9="김모")*(C4:C9="여")*D4:D9) 

   - B4:B9 : 이름 전체범위

   - B4:B9="김모" : 이름이 "김모"

   - C4:C9 : 성별 전체범위

   - C4:C9="여" : 성별이 "여"인 조건

   - (B4:B9="김모") * (C4:C9="여") : 가운데 "*"로 조건을 연결시킨다.(계속연결가능)

   - 마지막에는 실제 합계를 구하고자 하는 출장비 전체범위를 "*"로 연결한다

   - 여기까지는 일반적인 사항임.

   

2) 배열함수적용   

=SUM((B4:B9="김모")*(C4:C9="여")*D4:D9) 

   - 여기까지 입력 후 수식 제일 오른쪽에 커서를 두고 CTRL + SHIFT + ENTER 키를 같이 눌러본다. 

     그러면 아래와 같이 수식에 중괄호가 덫씌워져 표시된다.    

={SUM((B4:B9="김모")*(C4:C9="여")*D4:D9)}

   - 그러면 아래와 같은 원하는 결과가 나올것이다.



만족하셨나요? ~~~~~~~



1. Kanboard 란?

  : KANBAN이란 도요다 자동차의 현장에서 작업대상들을 각각 적은 마분지 카드를 보드에 붙여넣은 다음 

    작업대상, 작업준비중, 진행중, 완료 이런식(단계는 넣을수도 있고 뺄수도 있음)으로 구분하여 생산진행과정을 

    관리 방식에서 유래되었다. (각 단계가 완료되면 그 다음단계에 마분지를 옮겨붙이는 방식)

    기것을 기반으로 만들어진 프로그램이 Kanboard다.

    요즘 유행하는 Scrum이 여기서부터 시작된 듯...


2. 프로그램 설명

 - 사이트 : https://kanboard.org/

 - 라이센스 : MIT (기업에서도 무료로 사용할 수 있는 라이센스, 수정가능)

 - 소스 다운로드 : https://github.com/kanboard/kanboard/releases

 - 언어 : PHP

 - 기타 : 한글지원

 - Kanban board 형태로 보기

   : 마우스로 해당항목을 옮기면 진행상태가 바로 업데이트된다. 

  - 목록으로 보기



  - Gantt Chart 추가가능

  : 차트 內에서 기간을 움직이면 자동으로 날짜가 수정됨.


3. 설치

   1) 파일 다운로드

      : https://github.com/kanboard/kanboard/releases 에서 최신버전을 다운로드 받아(zip) 압축을 푼다.

      - 압축을 풀면 아래와 같은 폴더구조가 나온다.


   2) 일반적인 저렴한(월 1,000원정도) 웹호스팅 서버(Apache서버+PHP가 설치된)에 FTP로 접속해서 

       Apache서버 root 폴더인 public_html 폴더 아래에 압축을 푼 파일을 넣는다.

   


4. 본인이 가입한 웹호스팅 url로 접속해본다.

   : 그런다음 기본적인 셋팅을 따라하면 됨.


만족하셨나요? ~~~~~~~



앞에 "제품정보"에서 "원산지"항목을 DB공통코드와 연동하여 콤보박스로 만드는 방법에 대해 설명하겠음.


1. 공토통코드관리에 원산지 추가

   - 아래 이미지와 같이 한국,미국,일본 원산지 항목을 추가한다.

   - 분류코드 : ORIGIN

   - 분류명 : 원산지

   - 코드는 각각 : KR, US, JP 로 입력

   - 코드값에 각각 : 한국,미국,일본 을 입력

   - 정렬 : 원하는 정렬순으로 숫자를 입력한다.

   - 사용여부 : 모두체크


2. javascript 수정

 - 앞에서 만든 webapp/assets/js/view/shopmng/product.js 를 수정한다.

var fnObj = {};

var ACTIONS = axboot.actionExtend(fnObj, {

    PAGE_SEARCH: function (caller, act, data) {

        axboot.ajax({

            type: "GET",

            url: ["product"],

            data: caller.searchView.getData(),

            callback: function (res) {

                caller.gridView01.setData(res);

            }

        });

        return false;

    },

    PAGE_SAVE: function (caller, act, data) {

        var saveList = [].concat(caller.gridView01.getData("modified"));

        saveList = saveList.concat(caller.gridView01.getData("deleted"));


        axboot.ajax({

            type: "PUT",

            url: ["product"],

            data: JSON.stringify(saveList),

            callback: function (res) {

                ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

                axToast.push(LANG("onsave"));

            }

        });

    },

    ITEM_ADD: function (caller, act, data) {

        caller.gridView01.addRow();

    },

    ITEM_DEL: function (caller, act, data) {

        caller.gridView01.delRow("selected");

    }

});


var CODE = {};  //추가

// fnObj 기본 함수 스타트와 리사이즈

fnObj.pageStart = function () {

    var _this = this;  


    axboot

        .call({

            type: "GET", url: "/api/v1/commonCodes", data: {groupCd: "ORIGIN"},

            callback: function (res) {

                var originList = [];

                res.list.forEach(function (n) {

                originList.push({

                                //여기서 CD, NM의 문자를 사용한 이유는 아래 gridView의 원산지의

                                // optionValue와 optionText를 CD와 NM으로 사용했기 때문이다.

                CD: n.code, NM: n.name + "(" + n.code + ")"                        

                    });

                });

                this.originList = originList;

                //alert(JSON.stringify(this.originList));

            }

        })

        .done(function () {

            CODE = this; // this는 call을 통해 수집된 데이터들.

//위쪽 추가

            _this.pageButtonView.initView();  // this --> _this 로 변경

            _this.searchView.initView();  // this --> _this 로 변경

            _this.gridView01.initView();  // this --> _this 로 변경

            ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

        });  //추가

};


fnObj.pageResize = function () {


};



fnObj.pageButtonView = axboot.viewExtend({

    initView: function () {

        axboot.buttonClick(this, "data-page-btn", {

            "search": function () {

                ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

            },

            "save": function () {

                ACTIONS.dispatch(ACTIONS.PAGE_SAVE);

            }

        });

    }

});


//== view 시작

/**

 * searchView

 */

fnObj.searchView = axboot.viewExtend(axboot.searchView, {

    initView: function () {

        this.target = $(document["searchView0"]);

        this.target.attr("onsubmit", "return ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);");

        this.filter = $("#filter");

    },

    getData: function () {

        return {

            pageNumber: this.pageNumber,

            pageSize: this.pageSize,

            filter: this.filter.val()

        }

    }

});



/**

 * gridView

 */

fnObj.gridView01 = axboot.viewExtend(axboot.gridView, {

    initView: function () {

        var _this = this;

        this.originList = CODE.originList; //추가

        

        this.target = axboot.gridBuilder({

            showRowSelector: true,

            frozenColumnIndex: 0,

            sortable: true,

            multipleSelect: true,

            target: $('[data-ax5grid="grid-view-01"]'), 

            columns: [

            {key: "prdtCd", label: "제품코드", width: 100, align: "center", editor: "text"},

                {key: "prdtNm", label: "제품명", width: 200, align: "center", editor: "text"},

                {

                    key: "origin", label: "원산지", width: 100,align: "center", editor: {

                    type: "select", config: {

                        columnKeys: {

                            optionValue: "CD", optionText: "NM"

                        },

                        options: this.originList //추가

                        /*

                            // this.originList 에 아래와 같이 셋팅되어있다. 만약 연동하지 않고 하드코딩으로 

                            // 처리하고 싶으면 this.originList 자리에 아래와 같은 형태로만 입력시켜주면 된다.

                        [

                        {"CD":"KR","NM":"한국(KR)"},

                        {"CD":"US","NM":"미국(US)"},

                        {"CD":"JP","NM":"일본(JP)"}

                        ]

                        */

                        

                    }

                  }

                },

                {key: "purchasePrice", label: "매입가격", width: 150, align: "right", editor: "number"},

                {key: "salesPrice", label: "판매가격", width: 150, align: "right", editor: "number"}          

            ],

            body: {

                onClick: function () {

                    this.self.select(this.dindex, {selectedClear: true});

                }

            }

        });


        axboot.buttonClick(this, "data-grid-view-01-btn", {

            "add": function () {

                ACTIONS.dispatch(ACTIONS.ITEM_ADD);

            },

            "delete": function () {

                ACTIONS.dispatch(ACTIONS.ITEM_DEL);

            }

        });

    },

    getData: function (_type) {

        var list = [];

        var _list = this.target.getList(_type);


        if (_type == "modified" || _type == "deleted") {

            list = ax5.util.filter(_list, function () {

                return this.prdtCd;

            });

        } else {

            list = _list;

        }

        return list;

    },

    addRow: function () {

        this.target.addRow({__created__: true}, "last");

    }

}); 


3. 화면확인 

  - 콤보박스가 잘 나오는 것을 볼 수 있다.



만족하셨나요? ~~~~~~~맘에 드셨다면 아래 꾹~~~


◆ 정상적인 CRUD를 위한 JAVA소스 수정


1. 먼저 maven 의 generate-sources 를 해준다.    


           그러면 아래와 같이 target하위에 소스가 생긴다.


2.  JAVA 소스 수정

 - com/dasdes/shopmng/contrillers/ProductController.java : 조회조건 받을수 있도록 수정 

 - com/dasdes/shopmng/domain/prdt/Product.java           : 필수값체크

 - com/dasdes/shopmng/domain/BaseService.java             : Entity 와 연결되는 QProduct.java(위 generate에서 생성됨) 선언

 - com/dasdes/shopmng/domain/prdt/ProductService.java  : 조회조건 filter추가 및 list 리턴객체 교체


  1) ProductController.java 수정

package com.dasdes.shopmng.controllers;


import com.chequer.axboot.core.api.response.Responses;

import com.chequer.axboot.core.controllers.BaseController;

import com.chequer.axboot.core.parameter.RequestParams;

import org.springframework.stereotype.Controller;

import com.chequer.axboot.core.api.response.ApiResponse;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import com.dasdes.shopmng.domain.prdt.Product;

import com.dasdes.shopmng.domain.prdt.ProductService;

import com.wordnik.swagger.annotations.ApiImplicitParam;  //추가

import com.wordnik.swagger.annotations.ApiImplicitParams; //추가


import javax.inject.Inject;

import java.util.List;


@Controller

@RequestMapping(value = "/api/v1/product")

public class ProductController extends BaseController {


    @Inject

    private ProductService productService;


    @RequestMapping(method = RequestMethod.GET, produces = APPLICATION_JSON)

    @ApiImplicitParams({

            @ApiImplicitParam(name = "prdtCd", value = "제품코드", dataType = "String", paramType = "query"),

            @ApiImplicitParam(name = "prdtNm", value = "제품명", dataType = "String", paramType = "query"),

            @ApiImplicitParam(name = "filter", value = "검색어", dataType = "String", paramType = "query")

    })    

    public Responses.ListResponse list(RequestParams<Product> requestParams) {

        List<Product> list = productService.gets(requestParams);

        return Responses.ListResponse.of(list);

    }


    @RequestMapping(method = {RequestMethod.PUT}, produces = APPLICATION_JSON)

    public ApiResponse save(@RequestBody List<Product> request) {

        productService.savePrdt(request);

        return ok();

    }

}


  2) Product.java 수정

package com.dasdes.shopmng.domain.prdt;


import com.chequer.axboot.core.annotations.ColumnPosition;

import com.dasdes.shopmng.domain.SimpleJpaModel;

import lombok.*;

import org.apache.ibatis.type.Alias;

import org.hibernate.annotations.DynamicInsert;

import org.hibernate.annotations.DynamicUpdate;

import com.chequer.axboot.core.annotations.Comment;

import javax.persistence.*;


import javax.validation.constraints.NotNull;



@Setter

@Getter

@DynamicInsert

@DynamicUpdate

@Entity

@EqualsAndHashCode(callSuper = true)

@Table(name = "prdt_base")

@Comment(value = "")

@Alias("product")

public class Product extends SimpleJpaModel<String> {


@Id

@Column(name = "prdt_cd", length = 50, nullable = false)

@NotNull(message = "제품코드를 입력하세요")   //pk이므로 not null 체크 추가

@Comment(value = "제품코드")

private String prdtCd;


@Column(name = "prdt_nm", length = 50, nullable = false)

@Comment(value = "제품명")

private String prdtNm;


@Column(name = "origin", length = 50, nullable = false)

@Comment(value = "원산지")

private String origin;


@Column(name = "purchase_price", precision = 10, nullable = false)

@Comment(value = "매입가격")

private Integer purchasePrice;


@Column(name = "sales_price", precision = 10, nullable = false)

@Comment(value = "판매가격")

private Integer salesPrice;



    @Override

    public String getId() {

        return prdtCd;

    }


3) BaseService.java 수정

package com.dasdes.shopmng.domain;


import com.dasdes.shopmng.domain.code.QCommonCode;

import com.dasdes.shopmng.domain.file.QCommonFile;

import com.dasdes.shopmng.domain.program.QProgram;

import com.dasdes.shopmng.domain.program.menu.QMenu;

import com.dasdes.shopmng.domain.user.QUser;

import com.dasdes.shopmng.domain.user.auth.QUserAuth;

import com.dasdes.shopmng.domain.user.auth.menu.QAuthGroupMenu;

import com.dasdes.shopmng.domain.user.role.QUserRole;

import com.chequer.axboot.core.domain.base.AXBootBaseService;

import com.chequer.axboot.core.domain.base.AXBootJPAQueryDSLRepository;

import com.dasdes.shopmng.domain.prdt.QProduct;  //추가


import java.io.Serializable;



public class BaseService<T, ID extends Serializable> extends AXBootBaseService<T, ID> {


    protected QUserRole qUserRole = QUserRole.userRole;

    protected QAuthGroupMenu qAuthGroupMenu = QAuthGroupMenu.authGroupMenu;

    protected QCommonCode qCommonCode = QCommonCode.commonCode;

    protected QUser qUser = QUser.user;

    protected QProgram qProgram = QProgram.program;

    protected QUserAuth qUserAuth = QUserAuth.userAuth;

    protected QMenu qMenu = QMenu.menu;

    protected QCommonFile qCommonFile = QCommonFile.commonFile;

    protected QProduct qProduct = QProduct.product; //추가


    protected AXBootJPAQueryDSLRepository<T, ID> repository;


    public BaseService() {

        super();

    }


    public BaseService(AXBootJPAQueryDSLRepository<T, ID> repository) {

        super(repository);

        this.repository = repository;

    }

}


4) ProductService.java 수정

package com.dasdes.shopmng.domain.prdt;


import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional; //추가


import com.dasdes.shopmng.domain.BaseService;

import com.querydsl.core.BooleanBuilder;  //추가


import javax.inject.Inject;

import com.chequer.axboot.core.parameter.RequestParams;

import java.util.List;


@Service

public class ProductService extends BaseService<Product, String> {

    private ProductRepository productRepository;


    @Inject

    public ProductService(ProductRepository productRepository) {

        super(productRepository);

        this.productRepository = productRepository;

    }


    public List<Product> gets(RequestParams<Product> requestParams) {

    String prdtCd=requestParams.getString("prdtCd", "");

    String prdtNm=requestParams.getString("prdtNm", "");

    String filter = requestParams.getString("filter");

   

    BooleanBuilder builder = new BooleanBuilder();



        if (isNotEmpty(prdtCd)) {

            builder.and(qProduct.prdtCd.eq(prdtCd));

        }


        if (isNotEmpty(prdtNm)) {

            builder.and(qProduct.prdtNm.eq(prdtNm));

        }


        List<Product> prdtList = select().from(qProduct).where(builder).orderBy(qProduct.prdtCd.asc(), qProduct.prdtNm.asc()).fetch();


        if (isNotEmpty(filter)) {

        prdtList = filter(prdtList, filter);

        }

   

        return prdtList;

    }

    

    //저장

    @Transactional

    public void savePrdt(List<Product> product) {

        save(product);

    }

}


3. 서버구동 및 CRUD 테스트

1) 저장/수정테스트 : 정상작동 OK


2) 검색필터 테스트 : 필터링 잘 됨. OK



Axboot 신규화면 만들기 4 - Detail 내역 컬럼 콤보박스 만들기(DB연동) 에서 계속........


만족하셨나요? ~~~~~~~

+ Recent posts