require 'csv' module JapanShippingCSV module Ehiden FieldSpec = Struct.new(:header, :description, :is_required, :max_length, :valid_encodings, :valid_values, :default_value) do def encoding_errors(value) if !is_required && (value.nil? || value.empty?) return [] end if is_required && (value.nil? || value.empty?) return ["value is required"] end results = [] if !valid_values.nil? && !valid_values.include?(value) results << "must be one of #{valid_values.join(", ")}" end if value.length > max_length results << "is longer than #{max_length} characters" end if valid_encodings # TODO # go through each character in the value, and veriy that it matches the valid encodings if !Encoding::is_valid_string(value, valid_encodings) results << "contains invalid character" end end results end end class Field attr_reader :spec, :value def initialize(spec, value) @spec = spec @value = value || @spec.default_value @value = Encoding::reencode(@value, @spec.valid_encodings) end def encoding_errors @spec.encoding_errors(@value) end end class CSV def initialize(options = {}) package_codes = ["001", "002", "003", "004", "005", "006", "007", "008"] # auto packageCodes = QStringList() # << QString("|") # << QString("001|%1").arg(tr("Boxed Goods")) # << QString("002|%1").arg(tr("Bagged Goods")) # << QString("003|%1").arg(tr("Suitcase")) # << QString("004|%1").arg(tr("Envelope")) # << QString("005|%1").arg(tr("Golf Bag")) # << QString("006|%1").arg(tr("Skis")) # << QString("007|%1").arg(tr("Snowboard")) # << QString("008|%1").arg(tr("Other")) delivery_speeds = ["000", "001", "002", "003", "004", "005"] # << QString("|") # << QString("000|%1").arg(tr("Courier Service")) # << QString("001|%1").arg(tr("Supermarket")) # << QString("002|%1").arg(tr("Immediate Flight")) # << QString("003|%1").arg(tr("Air Mail (next day delivery)")) # << QString("004|%1").arg(tr("Air Mail (next morning delivery)")) # << QString("005|%1").arg(tr("\"Just Time\" Flight")) delivery_types = ["001", "002", "003"] # << QString("|") # << QString("001|%1").arg(tr("Regular")) # << QString("002|%1").arg(tr("Cool Shipping")) # << QString("003|%1").arg(tr("Frozen Shipping")) time_types = ["01", "12", "14", "16", "04"] # << QString("|") # << QString("01|%1").arg(tr("09:00-12:00")) # << QString("12|%1").arg(tr("12:00-14:00")) # << QString("14|%1").arg(tr("14:00-16:00")) # << QString("16|%1").arg(tr("16:00-18:00")) # << QString("04|%1").arg(tr("18:00-21:00")) seal_types = ["001", "002", "005", "007", "008", "016"] # << QString("|") # << QString("001|%1").arg(tr("Cool Shipping")) # << QString("002|%1").arg(tr("Frozen Shipping")) # << QString("005|%1").arg(tr("Specific Day Delivery")) # << QString("007|%1").arg(tr("Specifc Time Delivery")) # << QString("008|%1").arg(tr("Cash-On-Delivery")) # << QString("016|%1").arg(tr("Specific Time Delivery (AM)")) defaults = options[:default_field_values] || {} @field_infos = { address_code: FieldSpec.new("住所録コード", "Address Code", false, 12, Encoding::HALFWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_phone_number: FieldSpec.new("お届け先電話番号", "Shipping Phone Number", false, 14, Encoding::HALFWIDTH | Encoding::NUMBERS | Encoding::HYPHEN, nil, nil), shipping_postal_code: FieldSpec.new("お届け先郵便番号", "Shipping Postal Code", false, 8, Encoding::HALFWIDTH | Encoding::NUMBERS | Encoding::HYPHEN, nil, nil), shipping_address1: FieldSpec.new("お届け先住所1", "Shipping Address 1", true, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_address2: FieldSpec.new("お届け先住所2", "Shipping Address 2", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_address3: FieldSpec.new("お届け先住所3", "Shipping Address 3", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_name1: FieldSpec.new("お届け先名称1", "Shipping Name 1", true, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_name2: FieldSpec.new("お届け先名称2", "Shipping Name 2", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_customer_admin_number: FieldSpec.new("お客様管理ナンバー", "Shipping Customer Admin Number", false, 16, Encoding::HALFWIDTH | Encoding::ROMAJI, nil, nil), customer_code: FieldSpec.new("お客様コード", "Customer Code", false, 12, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:customer_code]), person_and_division_in_charge: FieldSpec.new("部署・担当者", "Person And Division In Charge", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), shipping_persons_number: FieldSpec.new("荷送人電話番号", "Shipping Persons Number", false, 14, Encoding::HALFWIDTH | Encoding::NUMBERS | Encoding::HYPHEN, nil, defaults[:shipping_persons_number]), shipping_requestors_phone_number: FieldSpec.new("ご依頼主電話番号", "Shipping Requestors Phone Number", false, 14, Encoding::HALFWIDTH | Encoding::NUMBERS | Encoding::HYPHEN, nil, defaults[:shipping_requestors_phone_number]), shipping_requestors_postal_code: FieldSpec.new("ご依頼主郵便番号", "Shipping Requestors Postal Code", false, 8, Encoding::HALFWIDTH | Encoding::NUMBERS | Encoding::HYPHEN, nil, defaults[:shipping_requestors_postal_code]), shipping_requestors_address_line_1: FieldSpec.new("ご依頼主住所1", "Shipping Requestors Address Line 1", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, defaults[:shipping_requestors_address_line_1]), shipping_requestors_address_line_2: FieldSpec.new("ご依頼主住所2", "Shipping Requestors Address Line 2", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, defaults[:shipping_requestors_address_line_1]), shipping_requestors_name_line_1: FieldSpec.new("ご依頼主名称1", "Shipping Requestors Name Line 1", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, defaults[:shipping_requestors_name_line_1]), shipping_requestors_name_line_2: FieldSpec.new("ご依頼主名称2", "Shipping Requestors Name Line 2", false, 16, Encoding::FULLWIDTH | Encoding::ROMAJI | Encoding::NUMBERS | Encoding::KATAKANA, nil, nil), package_code: FieldSpec.new("荷姿コード", "Package Code", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, package_codes, defaults[:package_code]), item1: FieldSpec.new("品名1", "Item 1", false, 16, Encoding::FULLWIDTH, nil, nil), item2: FieldSpec.new("品名2", "Item 2", false, 16, Encoding::FULLWIDTH, nil, nil), item3: FieldSpec.new("品名3", "Item 3", false, 16, Encoding::FULLWIDTH, nil, nil), item4: FieldSpec.new("品名4", "Item 4", false, 16, Encoding::FULLWIDTH, nil, nil), item5: FieldSpec.new("品名5", "Item 5", false, 16, Encoding::FULLWIDTH, nil, nil), item_count: FieldSpec.new("出荷個数", "Item Count", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), delivery_speed: FieldSpec.new("便種(スピードで選択)", "Delivery Speed", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, delivery_speeds, defaults[:delivery_speed]), delivery_type: FieldSpec.new("便種(商品)", "Delivery Type", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, delivery_types, defaults[:delivery_type]), delivery_date: FieldSpec.new("配達日", "Delivery Date", false, 8, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), delivery_time1: FieldSpec.new("配達指定時間帯", "Delivery Time 1", false, 2, Encoding::HALFWIDTH | Encoding::NUMBERS, time_types, nil), delivery_time2: FieldSpec.new("配達指定時間(時分)", "Delivery Time 2", false, 4, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), cod_amount: FieldSpec.new("代引金額", "COD Amount", false, 7, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), included_tax_amount: FieldSpec.new("消費税", "Included Tax Amount", false, 6, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), cod_payment_method: FieldSpec.new("決済種別", "COD Payment Method", false, 1, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:cod_payment_method]), insured_amount: FieldSpec.new("保険金額", "Insured Amount", false, 8, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:insured_amount]), display_insured_amount: FieldSpec.new("保険金額印字", "Display Insured Amount", false, 1, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:display_insured_amount]), seal1: FieldSpec.new("指定シール1", "Seal 1", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, seal_types, nil), seal2: FieldSpec.new("指定シール2", "Seal 2", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, seal_types, nil), seal3: FieldSpec.new("指定シール3", "Seal 3", false, 3, Encoding::HALFWIDTH | Encoding::NUMBERS, seal_types, nil), stops_at_delivery_centre: FieldSpec.new("営業店止め", "Stops At Delivery Centre", false, 1, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:stops_at_delivery_centre]), sagawa_logistics_centre: FieldSpec.new("SRC区分", "Sagawa Logistics Centre", false, 1, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:sagawa_logistics_centre]), sagawa_centre_code: FieldSpec.new("営業店コード", "Sagawa Centre Code", false, 4, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, nil), payment_style: FieldSpec.new("元着区分", "Payment Style", false, 1, Encoding::HALFWIDTH | Encoding::NUMBERS, nil, defaults[:payment_style]) } # e.g., {0 => {shipping_name: Field}} @next_row_id = 0 @row_fields = Hash.new { |h, k| h[k] = Hash.new() } end def row_ids @row_fields.keys end def field_names @field_infos.keys end def add_row result = @next_row_id + 1 @next_row_id += 1 result end def set_field_value(row_id, field_name, field_value) row = @row_fields[row_id] field_info = @field_infos[field_name] raise "unrecognised field #{field_name}" if field_info.nil? field = Field.new(field_info, field_value) row[field_name] = field end def add_row_values(values = {}) row_id = add_row values.each do |field_name, field_value| set_field_value(row_id, field_name, field_value) end row_id end def get_field_value(row_id, field_name) row = @row_fields[row_id] field_info = @field_infos[field_name] field = row[field_name] if field.nil? field = Field.new(field_info, nil) end field.value || "" end def get_field_error(row_id, field_name) row = @row_fields[row_id] field_info = @field_infos[field_name] field = row[field_name] if field.nil? field = Field.new(field_info, nil) end field.encoding_errors || "" end def encoding_errors result = {} @row_fields.each do |row_id, fields| errors = fields.select { |_n, f| f.encoding_errors.any? } result[row_id] = errors unless errors.empty? end result end def to_csv(options = {}) field_headers = @field_infos.map { |_k, v| v.header.encode(::Encoding::SJIS) } ::CSV.generate(options) do |csv| csv << field_headers @row_fields.each do |row| row_data = field_names.map do |field_name| field_value = get_field_value(row[0], field_name) begin field_value.gsub("−", "‐").encode(::Encoding::SJIS) rescue => ex raise "could not encode field #{field_name} (value #{field_value}) - #{ex.message}" end end csv << row_data end end end end end end