728x90

프로그램 작업을 하다 보면 견적서 같은 작업에서 견적서 번호를 유니크하게 생성해야 하는 경우가 종종 있다. 고객사에서는 번호를 한달에 몇건을 체크하고 싶어서 순차적으로 증가되게 해달라고 요청하시는 경우가 종종 있다. 그래서 주로 날짜와 뒷번호는 순번으로 채워지곤 하는데 아래 예시는 일자별 견적서 번호를 유니크하게 생성하는 로직이다. 

 

이때는 테이블 하나에서 해결하려고 하면 많이 복잡해 지니 차라리 번호만 관리하는 테이블을 하나 더 만드는것이 좋다. 또한 다른사람이 동일하게 접속해서 들어가더라도 순서대로 번호가 증가해서 그 키값을 가지고 있는거라 예약번호나 견적서 번호 같은걸 생성 해야 할때 참고 하면 좋겠다.  

CREATE TABLE temp_estimate_numbers (
    id INT AUTO_INCREMENT PRIMARY KEY,
    est_number VARCHAR(20) NOT NULL UNIQUE
);

 

테이블 쿼리문과 번호 생성하는 코드를 php 로 아래와 같이 메모해 본다. 

$pdo = dbconn(); // 데이터베이스 연결

// 견적서 번호 생성
$date = date('ymd');
$max_number = QRY_MAX("temp_estimate_numbers", " AND est_number LIKE '$date%'", "est_number");

if ($max_number) {
    $sequence = intval(substr($max_number, -3)) + 1; // 마지막 3자리에서 순번 증가
} else {
    $sequence = 1; // 기존 주문이 없으면 1로 시작
}

$orderNumber = str_pad($sequence, 3, '0', STR_PAD_LEFT);
$estimateNumber = $date . '-' . $orderNumber;

// 임시 테이블에 저장
$insert_sql = "INSERT INTO temp_estimate_numbers (est_number) VALUES ('$estimateNumber')";
$stmt = $pdo->prepare($insert_sql);

// 실행
if ($stmt->execute()) {
    echo "견적서 번호가 성공적으로 저장되었습니다: " . $estimateNumber;
} else {
    echo "저장 중 오류 발생.";
}
728x90
728x90

php 에서 pdf 파일로 다운로드 하는 방법은 몇가지가 있는데 그중 컴포저를 설치 했을 경우의 예시를 들어 본다. 아래와 같이 tecnickcom/tcpdf 로 컴포저를 설치하고 적용 방법은 아래에 3가지 경우로 예시를 메모해 본다. 

 

 

보통은 window.print() 로 날리면 해당 화면이 출력하기 모듈이 뜨면서 거기서 pdf 로 바로 다운로드가 가능한데 pdf 로 바로 다운이 되어야 하는 경우는 아래와 같이 수정을 해야 한다.  

composer require tecnickcom/tcpdf

 

1. tcpdf 를 사용해서 $pdf 로 생성하는 방법 

<?php
require_once('vendor/autoload.php');

// PDF 문서 생성
$pdf = new TCPDF();

// 문서 정보 설정
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Your Name');
$pdf->SetTitle('견적서');
$pdf->SetSubject('견적서 예제');
$pdf->SetKeywords('TCPDF, PDF, example, test, guide');

// 페이지 추가
$pdf->AddPage();

// 제목 추가
$pdf->SetFont('helvetica', 'B', 20); // 제목 폰트 설정
$pdf->Cell(0, 10, '견적서', 0, 1, 'C');
$pdf->Ln(10); // 줄 바꿈

// 테이블 데이터
$tableData = [
    ['항목', '수량', '단가', '합계'],
    ['상품 A', '2', '50000', '100000'],
    ['상품 B', '1', '30000', '30000'],
    ['상품 C', '3', '20000', '60000'],
];

// 테이블 스타일 설정
$pdf->SetFont('helvetica', 'B', 12); // 헤더 폰트 설정
$pdf->SetFillColor(200, 220, 255); // 헤더 배경 색상
$pdf->Cell(40, 10, $tableData[0][0], 1, 0, 'C', 1);
$pdf->Cell(40, 10, $tableData[0][1], 1, 0, 'C', 1);
$pdf->Cell(40, 10, $tableData[0][2], 1, 0, 'C', 1);
$pdf->Cell(40, 10, $tableData[0][3], 1, 1, 'C', 1);

// 테이블 데이터 출력
$pdf->SetFont('helvetica', '', 12); // 데이터 폰트 설정
$pdf->SetFillColor(255, 255, 255); // 데이터 배경 색상

foreach ($tableData as $key => $row) {
    if ($key > 0) { // 첫 번째 행은 이미 출력했으므로 생략
        $pdf->Cell(40, 10, $row[0], 1, 0, 'C', 1);
        $pdf->Cell(40, 10, $row[1], 1, 0, 'C', 1);
        $pdf->Cell(40, 10, $row[2], 1, 0, 'C', 1);
        $pdf->Cell(40, 10, $row[3], 1, 1, 'C', 1);
    }
}

// 하단에 직인 이미지 추가
$pdf->Ln(10);
$imageFile = 'path/to/your/seal.png'; // 직인 이미지 경로
$pdf->Image($imageFile, 150, 250, 40, 40, 'PNG', '', '', false, 300, '', false, false, 0, false, false, false);

// PDF 파일 다운로드
$pdf->Output('견적서.pdf', 'D');
?>

 

2. html 태그를 css 파일을 적용해서 중간에 추가 하는 경우

<?php
require_once('vendor/autoload.php');

// PDF 문서 생성
$pdf = new TCPDF();

// 문서 정보 설정
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Your Name');
$pdf->SetTitle('견적서');
$pdf->SetSubject('견적서 예제');
$pdf->SetKeywords('TCPDF, PDF, example, test, guide');

// 페이지 추가
$pdf->AddPage();

// CSS 파일 포함
$css = file_get_contents('path/to/styles.css'); // CSS 파일 경로
$html = '
<style>' . $css . '</style>
<h1>견적서</h1>
<table>
    <thead>
        <tr>
            <th>항목</th>
            <th>수량</th>
            <th>단가</th>
            <th>합계</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>상품 A</td>
            <td>2</td>
            <td>50000</td>
            <td>100000</td>
        </tr>
        <tr>
            <td>상품 B</td>
            <td>1</td>
            <td>30000</td>
            <td>30000</td>
        </tr>
        <tr>
            <td>상품 C</td>
            <td>3</td>
            <td>20000</td>
            <td>60000</td>
        </tr>
    </tbody>
</table>
<br>
<img src="path/to/your/seal.png" alt="직인" width="100" height="100" style="float:right;">
';

// HTML 콘텐츠 출력
$pdf->writeHTML($html, true, false, true, false, '');

// PDF 파일 다운로드
$pdf->Output('견적서.pdf', 'D');
?>

 

3. html 코드가 있는 php 파일을 include 해서 적용하는 방법

<?php
require_once('vendor/autoload.php');

// PDF 문서 생성
$pdf = new TCPDF();

// 문서 정보 설정
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Your Name');
$pdf->SetTitle('견적서');
$pdf->SetSubject('견적서 예제');
$pdf->SetKeywords('TCPDF, PDF, example, test, guide');

// 페이지 추가
$pdf->AddPage();

// HTML 파일 포함
ob_start(); // 출력 버퍼링 시작
include('aa.php'); // aa.php 파일 포함
$html = ob_get_clean(); // 버퍼 내용을 변수로 저장하고 버퍼 비우기

// HTML 콘텐츠 출력
$pdf->writeHTML($html, true, false, true, false, '');

// PDF 파일 다운로드
$pdf->Output('견적서.pdf', 'D');
?>

 

각 상황에 맞게 사용하는 것이 좋겠다. 컴포저 설치시에는 실서버와 동일한 php 버전에서 컴포저를 설치해야 버전 이슈가 없는 점은 참고해야 한다. 

728x90
728x90

약관동의 체크박스는 보기에는 간단한데 의외로 체크해야할 로직이 많다. 전체 동의를 하면 하위 체크박스가 모두 선택 되어야 하고 하나가 풀리면 전체동의 체크박스도 풀려야 하고 필수 동의 체크값에 따라 하위 버튼도 disabled 가 해제 되어야 해서 체크박스가 선택 될때마다 하단 등록 버튼 상태를 변경하는 로직은 한번 정리해두는게 좋다. 

 

아래 예시는 하단에 확인 버튼의 disabled 속성을 isConfirmDisabled 변수에 담고 체크박스의 값에 따라 watch 에서 체크 하는 로직으로 구현 하였다. 체크박스 상태가 변경 될때마다 확인이 되어야 하는 부분이어서 watch 로 걸어 주는것이 조금 더 편리하다. vue 에서 watch 는 너무 많이 걸면 조건이 꼬이기도 하니까 주의할 필요가 있다.  

<template>
  <CheckField
    v-for="(term, index) in terms"
    :key="index"
    v-model="term.agreed"
    :class="'terms-item'"
    :label="term.text"
    :required-indicator="term.required"
    :optional-indicator="!term.required"
    :show-view-button="index !== 0"
    :toggle-checkbox="index === 3 ? toggleMarketingConsent : () => {}"
    @view="() => clickConsent(term.content, index)"
  />
  <button :disabled="isConfirmDisabled">확인</button>
</template>

<script setup>
import { ref, watch } from 'vue';

const terms = ref([
  { agreed: false, text: '항목 1', required: true, content: '내용 1' },
  { agreed: false, text: '항목 2', required: true, content: '내용 2' },
  { agreed: false, text: '항목 3', required: false, content: '내용 3' },
  { agreed: false, text: '마케팅 동의', required: false, content: '내용 4' },
]);

const isConfirmDisabled = ref(true);

// 모든 항목의 동의 상태를 체크
watch(
  () => terms.value.map(term => term.agreed),
  (newAgreements) => {
    const allAgreed = terms.value[0]?.agreed && 
                      terms.value[1]?.agreed && 
                      terms.value[2]?.agreed;

    isConfirmDisabled.value = !allAgreed; // 모두 동의하지 않으면 버튼 비활성화
  },
  { immediate: true } // 컴포넌트 마운트 시 즉시 체크
);
</script>
728x90

+ Recent posts