Add localized names for csv header
This commit is contained in:
@@ -31,3 +31,8 @@ public extension PropertyIterable {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol ArrayDecodable {
|
||||
init(array: [String]) throws
|
||||
}
|
||||
|
||||
@@ -33,8 +33,11 @@ public enum GeneralError: Error {
|
||||
case invalidAccountNo
|
||||
case unsupportedQueryAtMockServer
|
||||
case emptyData
|
||||
case incorrectArrayItems
|
||||
case headerNoFiendName(String)
|
||||
}
|
||||
|
||||
|
||||
public enum QueryError: Error {
|
||||
case invalidUrl
|
||||
case invalidJson
|
||||
|
||||
@@ -63,8 +63,10 @@ public enum PeriodDivision: String, Codable, CustomStringConvertible {
|
||||
|
||||
/// 락 구분 코드
|
||||
public enum ExDivision: String, Codable {
|
||||
case none = ""
|
||||
|
||||
/// 해당사항없음 (락이 발생안한 경우)
|
||||
case none = "00"
|
||||
case notApplicable = "00"
|
||||
/// 권리락
|
||||
case exRights = "01"
|
||||
/// 배당락
|
||||
@@ -160,7 +162,7 @@ public struct CurrentPriceResult: Codable {
|
||||
/// ELW 발행 여부
|
||||
public let elwPublished: YesNo
|
||||
|
||||
/// 주식 현재가
|
||||
/// 주식 현재가 (주식 종가)
|
||||
public let currentStockPrice: String
|
||||
|
||||
/// 전일 대비
|
||||
@@ -182,7 +184,7 @@ public struct CurrentPriceResult: Codable {
|
||||
public let previousDayDiffVolumeRatio: String
|
||||
|
||||
/// 주식 시가
|
||||
public let stockPrice: String
|
||||
public let stockOpenningPrice: String
|
||||
|
||||
/// 주식 최고가
|
||||
public let highestStockPrice: String
|
||||
@@ -389,7 +391,7 @@ public struct CurrentPriceResult: Codable {
|
||||
case accumulatedVolume = "acml_vol"
|
||||
case previousDayDiffVolumeRatio = "prdy_vrss_vol_rate"
|
||||
|
||||
case stockPrice = "stck_oprc"
|
||||
case stockOpenningPrice = "stck_oprc"
|
||||
case highestStockPrice = "stck_hgpr"
|
||||
case lowestStockPrice = "stck_lwpr"
|
||||
case maximumStockPrice = "stck_mxpr"
|
||||
@@ -526,11 +528,11 @@ public struct MinutePriceResult: Codable {
|
||||
/// 누적 거래 대금
|
||||
public let accumulatedTradingAmount: String
|
||||
|
||||
/// 주식 현재가
|
||||
/// 주식 현재가 (주식종가)
|
||||
public let currentStockPrice: String
|
||||
|
||||
/// 주식 시가2
|
||||
public let secondStockPrice: String
|
||||
/// 주식 시가
|
||||
public let stockOpenningPrice: String
|
||||
|
||||
/// 주식 최고가
|
||||
public let highestStockPrice: String
|
||||
@@ -546,7 +548,7 @@ public struct MinutePriceResult: Codable {
|
||||
case stockConclusionTime = "stck_cntg_hour"
|
||||
case accumulatedTradingAmount = "acml_tr_pbmn"
|
||||
case currentStockPrice = "stck_prpr"
|
||||
case secondStockPrice = "stck_oprc"
|
||||
case stockOpenningPrice = "stck_oprc"
|
||||
case highestStockPrice = "stck_hgpr"
|
||||
case lowestStockPrice = "stck_lwpr"
|
||||
case conclusionVolume = "cntg_vol"
|
||||
@@ -556,16 +558,28 @@ public struct MinutePriceResult: Codable {
|
||||
return stockBusinessDate + stockConclusionTime
|
||||
}
|
||||
|
||||
public init(array: [String]) {
|
||||
public init(array: [String]) throws {
|
||||
guard array.count == 8 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.stockConclusionTime = array[1]
|
||||
self.accumulatedTradingAmount = array[2]
|
||||
self.currentStockPrice = array[3]
|
||||
self.secondStockPrice = array[4]
|
||||
self.stockOpenningPrice = array[4]
|
||||
self.highestStockPrice = array[5]
|
||||
self.lowestStockPrice = array[6]
|
||||
self.conclusionVolume = array[7]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 8))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +637,7 @@ public struct PeriodPriceResult: Codable {
|
||||
public let minimumStockPrice: String
|
||||
|
||||
/// 주식 시가
|
||||
public let stockPrice: String
|
||||
public let stockOpenningPrice: String
|
||||
|
||||
/// 주식 최고가
|
||||
public let highestStockPrice: String
|
||||
@@ -690,7 +704,7 @@ public struct PeriodPriceResult: Codable {
|
||||
case previousDayVolume = "prdy_vol"
|
||||
case maximumStockPrice = "stck_mxpr"
|
||||
case minimumStockPrice = "stck_llam"
|
||||
case stockPrice = "stck_oprc"
|
||||
case stockOpenningPrice = "stck_oprc"
|
||||
case highestStockPrice = "stck_hgpr"
|
||||
case lowestStockPrice = "stck_lwpr"
|
||||
case yesterdayStockPrice = "stck_prdy_oprc"
|
||||
@@ -845,7 +859,10 @@ public struct PeriodPriceResult: Codable {
|
||||
case revaluationIssueReason = "revl_issu_reas"
|
||||
}
|
||||
|
||||
public init(array: [String]) {
|
||||
public init(array: [String]) throws {
|
||||
guard array.count == 13 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.stockBusinessDate = array[0]
|
||||
self.stockClosingPrice = array[1]
|
||||
self.stockOpenningPrice = array[2]
|
||||
@@ -861,6 +878,15 @@ public struct PeriodPriceResult: Codable {
|
||||
self.revaluationIssueReason = array[12]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputPrice(array: Array(repeating: "", count: 13))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
|
||||
init(stockBusinessDate: String, stockClosingPrice: String, stockOpenningPrice: String, highestStockPrice: String, lowestStockPrice: String, accumulatedVolume: String, accumulatedTradingAmount: String, exDivision: ExDivision, partitionRate: String, partitionModifiable: YesNo, previousDayVariableRatioSign: String, previousDayVariableRatio: String, revaluationIssueReason: String) {
|
||||
self.stockBusinessDate = stockBusinessDate
|
||||
self.stockClosingPrice = stockClosingPrice
|
||||
|
||||
@@ -194,6 +194,47 @@ public struct BalanceResult: Codable {
|
||||
case substitutePrice = "sbst_pric"
|
||||
case stockLoanPrice = "stck_loan_unpr"
|
||||
}
|
||||
|
||||
init(array: [String]) throws {
|
||||
guard array.count == 26 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.productNo = array[0]
|
||||
self.productName = array[1]
|
||||
self.tradeDivisionCode = array[2]
|
||||
self.previousDayBuyQuantity = array[3]
|
||||
self.previousDaySellQuantity = array[4]
|
||||
self.todayBuyQuantity = array[5]
|
||||
self.todaySellQuantity = array[6]
|
||||
self.holdingQuantity = array[7]
|
||||
self.orderPossibleQuantity = array[8]
|
||||
self.averagePurchasePrice = array[9]
|
||||
self.purchaseAmount = array[10]
|
||||
self.currentPrice = array[11]
|
||||
self.evaluationAmount = array[12]
|
||||
self.evaluationProfitLossAmount = array[13]
|
||||
self.evaluationProfitLossRate = array[14]
|
||||
self.evaluationEarningRate = array[15]
|
||||
self.loanDate = array[16]
|
||||
self.loanAmount = array[17]
|
||||
self.shortSellingAmount = array[18]
|
||||
self.expiredDate = array[19]
|
||||
self.fluctuationRate = array[20]
|
||||
self.netChangeFluctuation = array[21]
|
||||
self.marginRequirementRatioName = array[22]
|
||||
self.depositRateName = array[23]
|
||||
self.substitutePrice = array[24]
|
||||
self.stockLoanPrice = array[25]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputStock(array: Array(repeating: "", count: 26))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
|
||||
public struct OutputAmount: Codable, PropertyIterable {
|
||||
@@ -295,6 +336,45 @@ public struct BalanceResult: Codable {
|
||||
case assetFluctuationAmount = "asst_icdc_amt"
|
||||
case assetFluctuationRate = "asst_icdc_erng_rt"
|
||||
}
|
||||
|
||||
init(array: [String]) throws {
|
||||
guard array.count == 24 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.depositTotalAmount = array[0]
|
||||
self.nextDayCalcAmount = array[1]
|
||||
self.nextTwoDayCalcAmount = array[2]
|
||||
self.cmaEvaluationAmount = array[3]
|
||||
self.previousDayBuyAmount = array[4]
|
||||
self.todayBuyAmount = array[5]
|
||||
self.nextDayAutoRedemptionAmount = array[6]
|
||||
self.previousDaySellAmount = array[7]
|
||||
self.todaySellAmount = array[8]
|
||||
self.nextTwoDayAutoRedemptionAmount = array[9]
|
||||
self.previousDayExpensesAmount = array[10]
|
||||
self.todayExpensesAmount = array[11]
|
||||
self.totalLoanAmount = array[12]
|
||||
self.securitiesEvaluationAmount = array[13]
|
||||
self.totalEvaluationAmount = array[14]
|
||||
self.netAssetAmount = array[15]
|
||||
self.loanAutoRedemptionAllowable = YesNo(rawValue: array[16])!
|
||||
self.purchaseAmountSum = array[17]
|
||||
self.evaluationAmountSum = array[18]
|
||||
self.evaluationProfitLossAmountSum = array[19]
|
||||
self.shortSellingAmountSum = array[20]
|
||||
self.previousDayAssetEvalutionSum = array[21]
|
||||
self.assetFluctuationAmount = array[22]
|
||||
self.assetFluctuationRate = array[23]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputAmount(array: Array(repeating: "", count: 24))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,40 @@ public struct VolumeRankResult: Codable {
|
||||
case nDayTradingAmountTurnoverRate = "nday_tr_pbmn_tnrt"
|
||||
case accumulatedTradingAmount = "acml_tr_pbmn"
|
||||
}
|
||||
|
||||
init(array: [String]) throws {
|
||||
guard array.count == 19 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.htsProductName = array[0]
|
||||
self.shortProductNo = array[1]
|
||||
self.dataRank = array[2]
|
||||
self.currentStockPrice = array[3]
|
||||
self.previousDayVariableRatioSign = array[4]
|
||||
self.previousDayVariableRatio = array[5]
|
||||
self.previousDayDiffRatio = array[6]
|
||||
self.accumulatedVolume = array[7]
|
||||
self.previousDayVolume = array[8]
|
||||
self.listedStockCount = array[9]
|
||||
self.averageVolume = array[10]
|
||||
self.nDayBeforeClosingPriceDiffRatio = array[11]
|
||||
self.volumeIncreaseRate = array[12]
|
||||
self.volumeTurnoverRate = array[13]
|
||||
self.nDayVolumeTurnoverRate = array[14]
|
||||
self.averageTradingAmount = array[15]
|
||||
self.tradingAmountTurnoverRate = array[16]
|
||||
self.nDayTradingAmountTurnoverRate = array[17]
|
||||
self.accumulatedTradingAmount = array[18]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! OutputDetail(array: Array(repeating: "", count: 19))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,14 +107,14 @@ extension DomesticShop {
|
||||
/// 시장 구분
|
||||
public let marketCategory: String
|
||||
|
||||
/// 종목명 (상품명)
|
||||
/// 종목명
|
||||
public let itemName: String
|
||||
|
||||
/// 법인등록번호
|
||||
public let corporationNo: String
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
private enum CodingKeys: String, CodingKey, CaseIterable {
|
||||
case baseDate = "basDt"
|
||||
case shortCode = "srtnCd"
|
||||
case isinCode = "isinCd"
|
||||
@@ -123,7 +123,10 @@ extension DomesticShop {
|
||||
case corporationNo = "crno"
|
||||
}
|
||||
|
||||
public init(array: [String]) {
|
||||
public init(array: [String]) throws {
|
||||
guard array.count == 6 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
self.baseDate = array[0]
|
||||
|
||||
/// shortCode 단축코드 명에 A000000 형태로 A문자가 붙어 있다.
|
||||
@@ -135,6 +138,15 @@ extension DomesticShop {
|
||||
self.itemName = array[4]
|
||||
self.corporationNo = array[5]
|
||||
}
|
||||
|
||||
public static func symbols() -> [String] {
|
||||
let i = try! Item(array: Array(repeating: "", count: 6))
|
||||
return Mirror(reflecting: i).children.compactMap { $0.label }
|
||||
}
|
||||
|
||||
public static func localizedSymbols() -> [String: String] {
|
||||
[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,4 +109,36 @@ extension Array where Element: PropertyIterable {
|
||||
}
|
||||
try stringCsv.write(toFile: toFile.path, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
static func readCsv(fromFile: URL, verifyHeader: Bool = true) throws -> [Element] where Element: ArrayDecodable {
|
||||
let stringCsv = try String(contentsOfFile: fromFile.path, encoding: .utf8)
|
||||
let items = stringCsv.split(separator: "\n")
|
||||
guard items.count > 0 else {
|
||||
return []
|
||||
}
|
||||
|
||||
var headerItems = [String]()
|
||||
var elements = [Element]()
|
||||
|
||||
for (index, item) in items.enumerated() {
|
||||
if index == 0 {
|
||||
headerItems = item.split(separator: ",").map { String($0) }
|
||||
continue
|
||||
}
|
||||
let array = item.split(separator: ",").map { String($0) }
|
||||
let element = try Element(array: array)
|
||||
|
||||
if index == 1, verifyHeader {
|
||||
// Validate property with header
|
||||
let properties = try element.allProperties()
|
||||
for (label, _) in properties {
|
||||
if false == headerItems.contains(where: { $0 == label }) {
|
||||
throw GeneralError.headerNoFiendName(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
elements.append(element)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,28 @@ extension KissConsole {
|
||||
var accountAmountUrl: URL {
|
||||
URL.currentDirectory().appending(path: "data/account-amount.csv")
|
||||
}
|
||||
|
||||
var localNamesUrl: URL {
|
||||
URL.currentDirectory().appending(path: "data/localized-names.csv")
|
||||
}
|
||||
|
||||
func candleFileUrl(productNo: String, period: CandleFilePeriod, day: String) -> URL {
|
||||
assert(day.count == 8)
|
||||
let subPath = "data/\(productNo)/\(period.rawValue)"
|
||||
let subFile: String
|
||||
|
||||
switch period {
|
||||
case .minute:
|
||||
subFile = "\(subPath)/candle-\(day).csv"
|
||||
case .day:
|
||||
subFile = "\(subPath)/candle-day-250.csv"
|
||||
case .weak:
|
||||
subFile = "\(subPath)/candle-week-42.csv"
|
||||
}
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +78,7 @@ extension KissConsole {
|
||||
continue
|
||||
}
|
||||
|
||||
let product = DomesticShop.Product(array: array)
|
||||
let product = try! DomesticShop.Product(array: array)
|
||||
if var value = products[product.itemName] {
|
||||
value.append(product)
|
||||
products.updateValue(value, forKey: product.itemName)
|
||||
@@ -135,7 +157,7 @@ extension KissConsole {
|
||||
continue
|
||||
}
|
||||
|
||||
let candle = Domestic.Candle(array: array)
|
||||
let candle = try! Domestic.Candle(array: array)
|
||||
candles.append(candle)
|
||||
}
|
||||
|
||||
@@ -177,3 +199,16 @@ extension BelongClassCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalName: Codable, PropertyIterable, ArrayDecodable {
|
||||
let fieldName: String
|
||||
let localizedName: String
|
||||
|
||||
init(array: [String]) throws {
|
||||
guard array.count == 2 else {
|
||||
throw GeneralError.incorrectArrayItems
|
||||
}
|
||||
fieldName = array[0]
|
||||
localizedName = array[1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,25 +25,6 @@ extension KissConsole {
|
||||
}
|
||||
|
||||
|
||||
func candleFileUrl(productNo: String, period: CandleFilePeriod, day: String) -> URL {
|
||||
assert(day.count == 8)
|
||||
let subPath = "data/\(productNo)/\(period.rawValue)"
|
||||
let subFile: String
|
||||
|
||||
switch period {
|
||||
case .minute:
|
||||
subFile = "\(subPath)/candle-\(day).csv"
|
||||
case .day:
|
||||
subFile = "\(subPath)/candle-day-250.csv"
|
||||
case .weak:
|
||||
subFile = "\(subPath)/candle-week-42.csv"
|
||||
}
|
||||
let fileUrl = URL.currentDirectory().appending(path: subFile)
|
||||
createSubpath(subPath)
|
||||
return fileUrl
|
||||
}
|
||||
|
||||
|
||||
func getRecentCandle(productNo: String, period: PeriodDivision, count: Int) async -> Bool {
|
||||
do {
|
||||
guard currentCandleShortCode == nil else {
|
||||
|
||||
@@ -58,6 +58,10 @@ class KissConsole {
|
||||
case love = "love" // love nuts.1 ISCD
|
||||
case hate = "hate" // hate nuts.1 ISCD
|
||||
|
||||
// 기타
|
||||
case localizeNames = "localize names"
|
||||
|
||||
|
||||
var needLogin: Bool {
|
||||
switch self {
|
||||
case .quit, .loginMock, .loginReal:
|
||||
@@ -72,7 +76,7 @@ class KissConsole {
|
||||
return false
|
||||
case .showcase:
|
||||
return false
|
||||
case .loves, .love, .hate:
|
||||
case .loves, .love, .hate, .localizeNames:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -193,6 +197,7 @@ class KissConsole {
|
||||
case .loves: await onLoves()
|
||||
case .love: await onLove(args)
|
||||
case .hate: await onHate(args)
|
||||
case .localizeNames: await onLocalizeNames()
|
||||
|
||||
default:
|
||||
print("Unknown command: \(line)")
|
||||
@@ -547,7 +552,7 @@ extension KissConsole {
|
||||
print("\t누적 거래 대금: ", output.accumulatedTradingAmount)
|
||||
print("\t누적 거래량: ", output.accumulatedVolume)
|
||||
print("\t전일 대비 거래량 비율: ", output.previousDayDiffVolumeRatio)
|
||||
print("\t주식 시가: ", output.stockPrice)
|
||||
print("\t주식 시가: ", output.stockOpenningPrice)
|
||||
print("\t주식 최고가: ", output.highestStockPrice)
|
||||
print("\t주식 최저가: ", output.lowestStockPrice)
|
||||
print("\t외국인 순매수 수량: ", output.foreignNetBuyingQuantity)
|
||||
@@ -846,6 +851,37 @@ extension KissConsole {
|
||||
print("Success \(love.name)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func onLocalizeNames() async {
|
||||
var symbols = Set<String>()
|
||||
|
||||
symbols.formUnion(DomesticShop.ProductResponse.Item.symbols())
|
||||
symbols.formUnion(BalanceResult.OutputStock.symbols())
|
||||
symbols.formUnion(BalanceResult.OutputAmount.symbols())
|
||||
symbols.formUnion(MinutePriceResult.OutputPrice.symbols())
|
||||
symbols.formUnion(PeriodPriceResult.OutputPrice.symbols())
|
||||
symbols.formUnion(VolumeRankResult.OutputDetail.symbols())
|
||||
let newNames = symbols.sorted(by: { $0 < $1 })
|
||||
|
||||
var added = 0
|
||||
var curNames = try! [LocalName].readCsv(fromFile: localNamesUrl, verifyHeader: true)
|
||||
for name in newNames {
|
||||
if false == curNames.contains(where: { $0.fieldName == name }) {
|
||||
let item = try! LocalName(array: [name, ""])
|
||||
curNames.append(item)
|
||||
added += 1
|
||||
}
|
||||
}
|
||||
if added > 0 {
|
||||
do {
|
||||
try curNames.writeCsv(toFile: localNamesUrl)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
print("Success \(localNamesUrl.lastPathComponent) total: \(curNames.count), new: \(added)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,12 +32,13 @@ WIP `modify (PNO) (ONO) (가격) (수량)` | 주문 내역을 변경. (수량)
|
||||
`candle week [PNO]` | 종목의 최근 52주 동안의 주봉 열람. PNO 은 생략 가능. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`candle week all` | 모든 종목의 최근 52주 동안의 주봉 열람. cron job 으로 오전 장이 시작전에 미리 수집. **data/(PNO)/week/candle-(yyyyMMdd).csv** 파일로 저장.
|
||||
`load shop` | data/shop-products.csv 로부터 전체 상품을 로딩.
|
||||
`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 data/shop-products.csv 로 저장.
|
||||
`update shop` | **금융위원회_KRX상장종목정보** 로부터 전체 상품을 얻어서 **data/shop-products.csv** 로 저장.
|
||||
`look (상품명)` | (상품명) 에 해당되는 PNO 를 표시함.
|
||||
WIP `showcase` | 추천 상품을 제안함.
|
||||
`loves` | 관심 종목 전체를 열람. profile.json 에 저장된 관심 종목을 표시함.
|
||||
`love (탭).(번호) (PNO)` | 관심 종목에 추가함. (번호) 를 지정하지 않으면 (탭) 마지막에 추가함.
|
||||
`hate (탭) (PNO)` | 관심 종목에서 삭제함.
|
||||
`localize names` | csv field name 에 대해서 한글명을 제공하는 **data/local-names.csv** 를 저장.
|
||||
|
||||
* PNO 는 `Product NO` 의 약자이고, 상품의 `단축코드` (shortCode) 와 동일합니다.
|
||||
* ONO 는 `Order NO` 의 약자이고, 고유한 주문번호 입니다.
|
||||
|
||||
2
bin/data
2
bin/data
Submodule bin/data updated: feebeed12f...01238b796d
Reference in New Issue
Block a user