2차과제 시작 전 pmd 에러잡기로 시작했다.
1.Apex classes should declare a sharing model if DML or SOQL/SOSL is used (rule: Default ruleset used by the CodeClimate Engine for Salesforce.com Apex-ApexSharingViolations)
⇒ with sharing 추가
2.Apex classes should escape/sanitize Strings obtained from URL parameters (rule: Default ruleset used by the CodeClimate Engine for Salesforce.com Apex-ApexXSSFromURLParam)
⇒.escapeHtml4() 추가로 injection 방지
3.Validate CRUD permission before SOQL/DML operation (rule: Default ruleset used by the CodeClimate Engine for Salesforce.com Apex-ApexCRUDViolation)
⇒ 권한 여부 체크를 통한 dml 에러 방지
if(!Schema.sObjectType.QuoteDocument.isCreateable()){
return 'fail';
}
4.Missing ApexDoc comment (rule: Default ruleset used by the CodeClimate Engine for Salesforce.com Apex-ApexDoc) * 3
⇒ 메서드가 아닌 클래스라 클래스용 주석은 달아놓고 내부 메서드 주석을 달지 않아 생긴 문제로
내부 메서드에 각자 주석을 달아 해결
5.Missing ApexDoc @description (rule: Default ruleset used by the CodeClimate Engine for Salesforce.com Apex-ApexDoc) * 3(class 2, test 1)
⇒분명 기존 템플릿에는 클래스는 Description으로 작성하게 되어있지만
@description 형태로 수정하라고 해서 수정해주니 3개의 클래스 모두 정상 처리되었다.
6.pmd랑 관련있어서 적어두는 report 생성 방법
pmd.bat -d "C:\Users\windmill\Desktop\Projects\practice\force-app\main\default\classes\QuotePDFController.cls" -f csv -R rulesets/apex/quickstart.xml -reportfile "C:\Users\windmill\Desktop\Projects\pmdreport.csv"
pmd.bat -d [source_directory] -f xml -R [rule_set_file] -r [output_file]
7.작은 이미지 사용시 썸네일로 용량 줄이기 아래와 같이 rendition = 사이즈 & 파일 id로 적용할 수 있다.
실제로 어떤 효과가 있는지 확인하기 위해 견적서 생성시 기존 이미지와
새로 적용된 썸네일의 네트워크 다운로드 데이터 양을 비교해봤다.
pdf자체 용량은 464b밖에 되지 않았는데 뭔가 비정상적으로 보였고
그 전에 이미 다 다운받은 내용을 pdf로 변환해서 그렇지 않나 생각된다.
자세한 비교를 위해 네트워크 다운로드 양을 찍은 후 썸네일로 변환해보니 차이가 없었다.

이유를 확인해보니 해당 파일은 썸네일을 지원하지 않았기 때문인데
다른 비교를 위해 공통 이미지를 넣어버렸다.
일단 차이는 모르겠지만
다운로드 횟수의 증가가 없기 때문에 썸네일이 더 좋을 것 같다고 이해하고 넘어갔다.
(기존 코드 = '/sfc/servlet.shepherd/version/download/0685i00000BWk41AAD’
썸네일 코드 = '/sfc/servlet.shepherd/version/renditionDownload?rendition=THUMB120BY90&versionId=0685i00000BWk41AAD’)
종료 후 1차 과제 코드 정리(org 삭제시 날아가는 것 방지)
//class
/****************************************************************************************
* File Name : QuoteDataController.cls
* @description : 견적서 처리를 위한 apex class로 vf page에 뿌려줄 데이터를 가져오고 pdf insert 기능을 보유하고 있다.
* Test Clss : QuoteDataControllerTest
* Author : Yohan
* Page : vf = QuotePdfPage // lwc = pdfCreateAction
* Modification Log
* ===============================================================
* Ver Date Author Modification
* ===============================================================
* 1.0 2023.04.28 Yohan 파일 생성 및 데이터 입력
* 1.1 2023.04.29 Yohan 유저, 상품목록, 상품 데이터 추가
* 2.0 2023.04.30 Yohan 상품목록, 상품 일치를 위한 productId 기준 정렬 , 타입지정용 class 생성
* 2.1 2023.05.02 Yohan PDF생성 메서드 추가, 네이밍 규칙 적용
* 2.2 2023.05.04 Yohan 네이밍규칙으로 인해 변경된 경로 수정
****************************************************************************************
* TODO
****************************************************************************************/
public with sharing class QuoteDataController {
public String quoteId = '';
public Quote quote = new Quote();
public User user = new User();
public List<QuoteLineItem> itemList = new List<QuoteLineItem>();
public List<Product2> productData = new List<Product2>();
public List<TableType> tableData = new List<TableType>();
/**
* @description : 순차적 쿼리 처리를 위한 컨스트럭터
* @author Yohan | 2023.04.28
**/
public QuoteDataController() {
quoteId = ApexPages.currentPage().getParameters().get('id').escapeHtml4();
quote = Database.query(
'SELECT Contact.Name, Id, OwnerId, Name, CreatedDate, CreatedById, OpportunityId, Pricebook2Id, ContactId, QuoteNumber, Tax, ExpirationDate, Description, Subtotal, TotalPrice, LineItemCount, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, BillingLatitude, BillingLongitude, BillingGeocodeAccuracy, BillingAddress, ShippingStreet, ShippingCity, ShippingState, ShippingPostalCode, ShippingCountry, ShippingLatitude, ShippingLongitude, ShippingGeocodeAccuracy, ShippingAddress, BillingName, ShippingName, Email, Phone, Fax, ContractId, AccountId, Discount, GrandTotal FROM Quote WHERE Id =:quoteId'
);
String qouteOwnerId = quote.OwnerId;
user = Database.query(
'SELECT CompanyName, Name, Email, Country FROM User WHERE Id = :qouteOwnerId'
);
itemList = Database.query(
'select LineNumber, Quantity, UnitPrice, Discount, Description, Product2Id, ListPrice, Subtotal, TotalPrice from QuoteLineItem where QuoteId = :quoteId ORDER BY Product2Id ASC'
);
productData = Database.query(
'select Id, Name, Description, ImgUrl__c from Product2 where Id in (select Product2Id from QuoteLineItem where QuoteId = :quoteId) ORDER BY Id ASC'
);
for (Product2 a : productData) {
a.DisplayUrl = '/servlet/servlet.FileDownload?file=' + a.ImgUrl__c;
// a.DisplayUrl = '/sfc/servlet.shepherd/version/renditionDownload?rendition=THUMB120BY90&versionId=0685i00000BWk41AAD';
// '/sfc/servlet.shepherd/version/download/0685i00000BWk41AAD'
}
for (Integer i = 0; i < itemList.size(); i++) {
tableData.add(new TableType(itemList[i], productData[i]));
}
}
/**
* @description : quoteId를 받아오는 getter
* @author Yohan | 2023.04.28
* @return String
**/
public String getQuoteId() {
return quoteId;
}
/**
* @description : quote를 받아오는 getter
* @author Yohan | 2023.04.28
* @return Quote
**/
public Quote getQuote() {
return quote;
}
/**
* @description : user를 받아오는 getter
* @author Yohan | 2023.04.28
* @return User
**/
public User getUser() {
return user;
}
/**
* @description : itemList를 받아오는 getter
* @author Yohan | 2023.04.28
* @return List<QuoteLineItem>
**/
public List<QuoteLineItem> getItemList() {
return itemList;
}
/**
* @description : tableData를 받아오는 getter
* @author Yohan | 2023.04.30
* @return List<TableType>
**/
public List<TableType> getTableData() {
return tableData;
}
/**
* @description : lwc에서 pdf 생성할 때 사용
* @author Yohan | 2023.05.01
* @param id
* @return String
**/
@AuraEnabled
public static String makePDF(String id) {
PageReference pdf = new pagereference('/apex/QuotePdfPage?id=' + id);
pdf.getParameters().put('id', id);
Blob body;
try {
body = pdf.getContent();
} catch (Exception e) {
body = Blob.valueOf('data');
}
if(!Schema.sObjectType.QuoteDocument.isCreateable()){
return 'fail';
}
insert new QuoteDocument(
Document = body,
QuoteId = id
);
return 'PDF creation success';
}
/****************************************************************************************
* File Name : QuoteDataController.cls
* @description : 타입지정을 위한 클래스
* Test Clss : QuoteDataControllerTest
* Author : Yohan
* Page : vf page
* Modification Log
* ===============================================================
* Ver Date Author Modification
* ===============================================================
* 1.0 2023.04.30 Yohan 타입지정을 위한 생성
****************************************************************************************
* TODO
****************************************************************************************/
public class TableType {
/**
* @description : 타입지정을 위한 클래스의 메서드
* @author Yohan | 2023.04.30
* @return QuoteLineItem
**/
public QuoteLineItem lineItem { get; set; }
/**
* @description : 타입지정을 위한 클래스의 메서드
* @author Yohan | 2023.04.30
* @return Product2
**/
public Product2 product { get; set; }
/**
* @description : 타입지정용 메서드
* @author Yohan | 2023.04.30
* @param lineItem
* @param product
**/
public TableType(QuoteLineItem lineItem, Product2 product) {
this.lineItem = lineItem;
this.product = product;
}
}
}
//test class
/****************************************************************************************
* File Name : QuoteDataControllerTest
* @description : getter로 데이터 받아지는지 확인 및 makePDF 파일 생성 확인
* Target : QuoteDataController.cls
* Author : Yohan
* Modification Log
* ===============================================================
* Ver Date Author Modification
* ===============================================================
* 1.0 2023.05.03 Yohan QuoteDataController getter, makePDF 테스트
****************************************************************************************/
@IsTest
public class QuoteDataControllerTest {
/**
* @description : Quote Test를 위해 testSetup으로 데이터 insert
* @author Yohan | 2023.05.03
**/
@testSetup
static void setup() {
//기회 제작
Opportunity testOpportunity = new Opportunity();
testOpportunity.Name = 'testOpportunity';
testOpportunity.StageName = 'Draft';
testOpportunity.CloseDate = Date.today().addDays(30);
insert testOpportunity;
//상품 제작
Product2 productA = new Product2();
productA.Name = 'productA';
insert productA;
Product2 productB = new Product2();
productB.Name = 'productB';
insert productB;
//상품목록 제작
Pricebook2 pb = new Pricebook2();
pb.Name = 'Test Pricebook';
pb.IsActive = true;
insert pb;
Id pricebookId = Test.getStandardPricebookId();
//상품목록 기입 제작
PricebookEntry spbEntryA = new PricebookEntry();
spbEntryA.UnitPrice = 500;
spbEntryA.Product2Id = productA.Id;
spbEntryA.Pricebook2Id = pricebookId;
spbEntryA.IsActive = true;
insert spbEntryA;
PricebookEntry spbEntryB = new PricebookEntry();
spbEntryB.UnitPrice = 1000;
spbEntryB.Product2Id = productB.Id;
spbEntryB.Pricebook2Id = pricebookId;
spbEntryB.IsActive = true;
insert spbEntryB;
//견적서 제작
Quote testQuote = new Quote();
testQuote.Name = 'Test Quote';
testQuote.OpportunityId = testOpportunity.id;
testQuote.Pricebook2Id = pricebookId;
testQuote.Tax = 0.1;
testQuote.ExpirationDate = Date.today().addDays(30);
testQuote.Description = 'Test Description';
testQuote.BillingStreet = '123 Main St';
testQuote.BillingName = 'Test Name';
testQuote.ShippingStreet = '123 Main St';
testQuote.ShippingName = 'Test Name';
testQuote.Email = 'test@example.com';
testQuote.Phone = '555-555-5555';
testQuote.Fax = '444-555-6666';
insert testQuote;
//견적서 상품리스트 제작
QuoteLineItem item1 = new QuoteLineItem();
item1.QuoteId = testQuote.Id;
item1.PricebookEntryId = spbEntryA.Id;
item1.Quantity = 2;
item1.UnitPrice = 500;
item1.Product2Id = productA.Id;
item1.Discount = 0.1;
item1.Description = 'Test Product 1';
insert item1;
QuoteLineItem item2 = new QuoteLineItem();
item2.QuoteId = testQuote.Id;
item2.PricebookEntryId = spbEntryB.Id;
item2.Quantity = 1;
item2.UnitPrice = 1000;
item2.Product2Id = productB.Id;
item2.Discount = 0.05;
item2.Description = 'Test Product 2';
insert item2;
}
/**
* @description : getter들 테스트를 위한 함수
* @author Yohan | 2023.05.03
**/
@isTest
static void testGetters() {
String id = [select id from quote].id;
ApexPages.currentPage().getParameters().put('id', id);
QuoteDataController controller = new QuoteDataController();
//각 getter 테스트
System.assertEquals(id, controller.getQuoteId());
System.assertEquals(controller.quote, controller.getQuote());
System.assertEquals(controller.user, controller.getUser());
System.assertEquals(controller.itemList, controller.getItemList());
System.assertEquals(controller.tableData, controller.getTableData());
}
/**
* @description : makePDF 테스트를 위한 테스트함수
* @author Yohan | 2023.05.03
**/
@isTest
static void testMakePDF() {
String id = [select id from quote].id;
Test.startTest();
QuoteDataController.makePDF(id);
Test.stopTest();
System.assertEquals(1, [select count() from QuoteDocument]);
}
}
//vf page
<!--
* ============================================================
* File Name : ContentMetaViewFile.page
* Function :
* Author : Yohan
* Date : 2023.05.02
* Description : LWC iframe에 들어갈 vf page
* Modification Log
* ============================================================
* Ver Date Author Modification
* ============================================================
1.0 2023.04.27 Yohan pdf 형태로 화면 출력
1.1 2023.04.28 Yohan 조회된 일부 값들 출력
1.2 2023.04.29 Yohan 조회된 값 및 이미지 출력
1.3 2023.04.30 Yohan 테이블 형태로 출력
2.0 2023.05.01 Yohan api 통합 관리로 인한 수정
2.1 2023.05.02 Yohan 한글로 통일 및 포맷 변경
2.2 2023.05.02 Yohan 파일명 파스칼케이스로 변경
-->
<apex:page Controller="QuoteDataController" showHeader="FALSE" standardStylesheets="false" renderas="pdf" applyBodyTag="false" applyHtmlTag="false">
<html>
<head>
<style>
@page {
font-family: 'Arial Unicode MS';
size: letter;
margin: 25mm;
@top-center {
content: {! user.CompanyName};
}
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
}
}
#header{
display: flex;
justify-content: space-between;
}
.half{
width: 40%;
}
.page-break {
display:block;
page-break-after:always;
}
body {
font-family: Arial Unicode MS;
font-size: 10px;
}
table{
width: 100%;
margin-left:auto;
margin-right:auto;
margin-bottom: 40px;
}
thead, tfoot{
background-color: rgb(243, 226, 226);
text-align: center;
}
tbody td{
margin: 0px;
border: 1px solid rgb(243, 226, 226);
text-align: right;
}
</style>
</head>
<body>
<table>
<tr>
<th style="font-size : 30px; vertical-align: text-top" >{! user.CompanyName}</th>
<th style="font-size : 13px; text-align: right;">
견적서 #{! quote.QuoteNumber}
<br />
생성일 : <apex:outputText value="{0,date,' 'yyyy'년 'MM'월 'dd'일'}"><apex:param value="{! quote.CreatedDate}"/></apex:outputText>
<br />
만기일 : <apex:outputText value="{0,date,' 'yyyy'년 'MM'월 'dd'일'}"><apex:param value="{! quote.ExpirationDate}"/></apex:outputText>
</th>
</tr>
</table>
<table cellpadding='10'>
<tr>
<th style="font-size : 13px; vertical-align: text-top" >
작성자 : {! user.Name}
<br />
이메일 : {! user.Email}
</th>
<th style="font-size : 13px; text-align: right;">
고객 : {! quote.Contact.Name}
<br />
연락처 : {! quote.Phone}
<br />
이메일 : {! quote.Email}
<br />
팩스 : {! quote.Fax}
</th>
</tr>
</table>
<div id="table1">
<table cellspacing='0' cellpadding='5'>
<thead>
<tr>
<th>상품명</th>
<th>이미지</th>
<th>리스트 가격</th>
<th>유닛 가격</th>
<th>수량</th>
<th>가격</th>
<th>할인율</th>
<th>할인가</th>
</tr>
</thead>
<tbody>
<apex:repeat value="{!tableData}" var="table">
<tr>
<td style="text-align: center;">{!table.product.Name}</td>
<td style="text-align: center;"><img src="{!table.product.DisplayUrl}" alt=" " style="height:50px; width:50px;" border="0"/></td>
<td>{!table.lineItem.ListPrice}</td>
<td>{!table.lineItem.UnitPrice}</td>
<td>{!table.lineItem.Quantity}</td>
<td>{!table.lineItem.Subtotal}</td>
<td>{!IF(ISNULL(table.lineItem.Discount), '0', table.lineItem.Discount)}%</td>
<td>{!table.lineItem.TotalPrice}</td>
</tr>
</apex:repeat>
</tbody>
<tfoot>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th>합계</th>
<th>{! quote.TotalPrice}</th>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
<div style="font-size : 13px; margin-left:60%;">
합계 : {! quote.Subtotal }<br />
할인 : {! quote.Subtotal - quote.TotalPrice}<br />
할인 적용가 : {! quote.TotalPrice}<br />
세금 : {! quote.Tax}<br />
배송비 : {! quote.GrandTotal - quote.TotalPrice -quote.Tax}<br />
총 지불액 : {! quote.GrandTotal}<br />
</div>
<div style="background-color:rgb(174, 217, 255); font-size:17px; margin-top:50px;"> 견적 수락 정보 </div>
<div>서명 _______________________________</div>
<div>이름 _______________________________</div>
<div>직책 _______________________________</div>
<div >날짜 _______________________________</div>
</apex:page>
//html
<template>
<section onclick={closeModal} if:true={isModalPoped} role="dialog" tabindex="-1" aria-modal="true"
aria-labelledby="modal-heading-01" class="slds-modal slds-fade-in-open slds-modal_medium">
<div onclick={stopPropagation} class="slds-modal__container" style="height:105vh">
<div class="slds-modal__header">
<h1 id="modal-heading-01" class="slds-modal__title slds-hyphenate">
PDF Preview
</h1>
</div>
<iframe onload={loadHandler} height="1000px" width="100%" src={quoteId}></iframe>
<img id="windmill" if:true={isLoading} src="https://i.ibb.co/9t77D7Y/image-removebg-preview-1.png">
<div class="slds-modal__footer">
<button onclick={closeModal} class="slds-button slds-button_neutral" aria-label="Cancel and close">
<label>취소</label>
</button>
<button onclick={handleDownloadPDF} class="slds-button slds-button_brand">
<label>PDF 저장</label>
</button>
</div>
</div>
</section>
<div if:true={isModalPoped} class="slds-backdrop slds-backdrop_open" role="presentation"></div>
<button onclick={openModal} class="slds-button slds-button_neutral">
<label>PDF 미리보기</label>
</button>
<button onclick={handleDownloadPDF} class="slds-button slds-button_neutral">
<label>PDF 생성</label>
</button>
<button onclick={openPDF} class="slds-button slds-button_neutral">
<label>PDF 새 탭에서 보기</label>
</button>
</template>
//js
import { LightningElement, track } from "lwc";
import makePDF from "@salesforce/apex/QuoteDataController.makePDF";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
export default class PdfCreateAction extends LightningElement {
quoteId = '/apex/QuotePdfPage?id=' + window.location.href.split('Quote/')[1].split('/')[0];
@track
isLoading = false
@track
isModalPoped = false;
closeModal() {
this.isModalPoped = false;
}
openModal() {
this.isModalPoped = true;
this.isLoading = true;
}
stopPropagation(e) {
e.stopPropagation();
}
handleDownloadPDF() {
try {
const tempQouteId = window.location.href.split('Quote/')[1].split('/')[0]
makePDF({id:tempQouteId})
.then(result => {
const successEvent = new ShowToastEvent({
title: "윈드밀소프트",
message: "견적서 생성 성공",
variant: "success"
});
this.dispatchEvent(successEvent);
this.closeModal();
})
.catch(error => {
const errorEvent = new ShowToastEvent({
title: "윈드밀소프트",
message: error + "견적서 생성 실패",
variant: "error"
});
this.dispatchEvent(errorEvent);
});
} catch (error) {
console.log(error);
}
}
openPDF(){
window.open('https://empathetic-wolf-5sx4qg-dev-ed--c.trailblaze.vf.force.com' + this.quoteId);
}
loadHandler(){
this.isLoading = false;
}
}
//css
@keyframes spin {
0% { transform: rotate(0deg); }
10% { transform: rotate(90deg); }
20% { transform: rotate(210deg); }
30% { transform: rotate(360deg); }
50% { transform: rotate(720deg); }
70% { transform: rotate(1440deg); }
100% { transform: rotate(2880deg); }
}
img {
animation: spin 3s linear infinite;
margin-left: 35%;
width : 30%;
position:absolute;
}
//meta
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>
점심을 먹고 썸네일 처리와 파일 동시 다운로드(압축형태)를 확인한 후
2차 과제를 시작했다.
2차과제에 사용될 내용이 어떤 이유로 필요한지 고민하다가
A회사의 데이터를 주기적으로 받아야 하는 상황이 있기 위해서는
B회사가 A회사의 재고에 관련이 있어야 한다고 생각했고
A회사가 재고량을 변경할 수 있다는 부분에 대해서
A회사는 생산쪽을 담당하는 회사라고 생각하고
B회사는 물류를 담당하며 판매를 진행하다가
재고가 부족할 경우 A회사의 제품을 추가적으로 발주하는 방향으로 생각했다.
A회사는 가죽관련 생산업체로 수제로 가죽 제품들을 만들며
B회사는 해당 제품을 판매하는 유통업체로 진행했다.
org의 이름은 각각 레더크래프트, 가죽연구소로 결정했으며
(leathercraft, leather lab)
A에도 생산 및 자체적 판매를 진행하며
B는 가죽 관련 판매 업체로 A외에도 여러 회사에서 물건을 구매해 판매한다.
org 세팅에 사용될 계정 두개
계정1 - rgc0582@wise-koala-w0yc3p.com
계정2 - rgc0582@mindful-otter-qmf3eu.com
일단 추가로 진행해야 하는 작업은
stock필드를 product2에 추가하고
필드 추가할 때는 description 추가도 잊지 말아야 한다.
그 뒤 A Org에서 Rest Api를 만들어
전체 품목의 상품명, 재고를 전송할 수 있게 하고
특정 품목들의 상품명, 재고만 받을 수도 있게 해야 한다.
B Org에서는 A에서 받은 정보를 따로 볼 수 있도록
A Org 재고 관리 전용 페이지를 따로 생성해서
A Org에서 주문할 수 있는 재고량을 확인할 수 있게 한다.
재고 관리 페이지에서 전체 동기화 및 부분 동기화(체크박스)가 가능하게 한다.
사용할 가죽 상품 목록은 46개로 아래와 같다.
에어팟 케이스, 롤링 펜 파우치, 핑거 스트랩 키링, 애플워치 스트랩, T자 키링, 바이폴드 월렛,
여권 케이스, 핸드 스트랩, 파우치 오일 풀업, 여권 슬리브, 메모리 컵 코스터, 카드 월렛,
펜슬 캡, 포켓 노트커버, 리갈패드 커버, 더블 웨이브 카드 월렛, 베루 웨이브 카드 월렛,
롤링 펜 파우치, 롤링 만년필 파우치, 베이직 노트커버, 스마트폰 파우치, 스마트키 케이스,
마그넷 키링, 애플펜슬 그립, 레더스킨 케이스, 펜 케이스, 브레스트 월렛, 트라이폴드 월렛,
아이패드 슬리브, 슬림 카드홀더, 안경 트레이, 맥북 슬리브, 롱 패드, 마우스 패드, 레더 트레이,
레더 벨트, 애플펜슬 홀더, 팔찌, ID 카드 홀더, 카메라 스트랩, 비즈니스 플래너 커버,
미니 피크 케이스, 필름 케이스, 4키 홀더, 3키 체인, 펜슬 케이스
A회사에서 상품 재고를 변경하는 경우 일반적인 업데이트를 진행하며
A회사에서 상품을 삭제하는 경우 재고를 0으로 바꾸고
(현재 판매중인 유통 상품이 있을 수 있기 때문에)
A회사에서 상품을 추가하는 경우 해당 상품을 B에도 생성(insert)한다.
부분조회시에는 해당 이름만 검색하기 때문에
추가는 고려할 필요가 없고 삭제, 변경만 확인하면 된다.
스케쥴은 아래와 같은 형태로 넣어주면 자동적으로 스케쥴 작업이 작동되기 때문에
api만 완성하면 될 것 같다.
System.schedule('Scheduled Job 1', '0 0 * * * ?', new scheduledTest());
실제로 1시간 간격으로 작업을 실행시키고 확인하니 계속해서 메서드가 돌아가는 것을 볼 수 있었다.


(1).백준 11320번 삼각무늬 - 1은 a, b 정삼각형 두개의 한 변의 길이가 주어졌을 때
a 정삼각형을 덮기 위해 b 정삼각형 몇개가 필요한지를 묻는 문제였다.
다 떠나서 넓이 자체가 제곱이기 때문에
a에서 b를 나눈 길이의 배수를 제곱하면 답이 도출된다.
const input = `2
2 1
3 3`.split('\n')
const result = []
for(let i = 1 ; i < input.length ; i++){
const [a,b] = input[i].split(' ').map(Number)
result.push((a/b)**2)
}
console.log(result.join('\n'))'회고' 카테고리의 다른 글
| [수습일지] - 45 (0) | 2023.05.10 |
|---|---|
| [수습일지] - 44 (0) | 2023.05.09 |
| [수습일지] - 42(주말) (0) | 2023.05.07 |
| [수습일지] - 41(주말) (0) | 2023.05.06 |
| [수습일지] - 40(어린이날) (0) | 2023.05.05 |
