7 changed files with 1149 additions and 1 deletions
			
			
		@ -1,5 +1,6 @@ | 
				
			|||||||
class Export < ApplicationRecord | 
					class Export < ApplicationRecord | 
				
			||||||
  scope :lettings, -> { where(collection: "lettings") } | 
					  scope :lettings, -> { where(collection: "lettings") } | 
				
			||||||
 | 
					  scope :sales, -> { where(collection: "sales") } | 
				
			||||||
  scope :organisations, -> { where(collection: "organisations") } | 
					  scope :organisations, -> { where(collection: "organisations") } | 
				
			||||||
  scope :users, -> { where(collection: "users") } | 
					  scope :users, -> { where(collection: "users") } | 
				
			||||||
end | 
					end | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,194 @@ | 
				
			|||||||
 | 
					module Exports::SalesLogExportConstants | 
				
			||||||
 | 
					  MAX_XML_RECORDS = 10_000 | 
				
			||||||
 | 
					  LOG_ID_OFFSET = 300_000_000_000 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXPORT_MODE = { | 
				
			||||||
 | 
					    xml: 1, | 
				
			||||||
 | 
					    csv: 2, | 
				
			||||||
 | 
					  }.freeze | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  EXPORT_FIELDS = Set["id", | 
				
			||||||
 | 
					                      "status", | 
				
			||||||
 | 
					                      "saledate", | 
				
			||||||
 | 
					                      "created_at", | 
				
			||||||
 | 
					                      "updated_at", | 
				
			||||||
 | 
					                      "owning_organisation_id", | 
				
			||||||
 | 
					                      "assigned_to_id", | 
				
			||||||
 | 
					                      "purchid", | 
				
			||||||
 | 
					                      "type", | 
				
			||||||
 | 
					                      "ownershipsch", | 
				
			||||||
 | 
					                      "othtype", | 
				
			||||||
 | 
					                      "jointmore", | 
				
			||||||
 | 
					                      "jointpur", | 
				
			||||||
 | 
					                      "beds", | 
				
			||||||
 | 
					                      "companybuy", | 
				
			||||||
 | 
					                      "ethnic", | 
				
			||||||
 | 
					                      "ethnic_group", | 
				
			||||||
 | 
					                      "buy1livein", | 
				
			||||||
 | 
					                      "buylivein", | 
				
			||||||
 | 
					                      "builtype", | 
				
			||||||
 | 
					                      "proptype", | 
				
			||||||
 | 
					                      "noint", | 
				
			||||||
 | 
					                      "buy2livein", | 
				
			||||||
 | 
					                      "privacynotice", | 
				
			||||||
 | 
					                      "wheel", | 
				
			||||||
 | 
					                      "hholdcount", | 
				
			||||||
 | 
					                      "la", | 
				
			||||||
 | 
					                      "la_known", | 
				
			||||||
 | 
					                      "income1", | 
				
			||||||
 | 
					                      "income1nk", | 
				
			||||||
 | 
					                      "details_known_2", | 
				
			||||||
 | 
					                      "details_known_3", | 
				
			||||||
 | 
					                      "details_known_4", | 
				
			||||||
 | 
					                      "inc1mort", | 
				
			||||||
 | 
					                      "income2", | 
				
			||||||
 | 
					                      "income2nk", | 
				
			||||||
 | 
					                      "savingsnk", | 
				
			||||||
 | 
					                      "savings", | 
				
			||||||
 | 
					                      "prevown", | 
				
			||||||
 | 
					                      "updated_by_id", | 
				
			||||||
 | 
					                      "income1_value_check", | 
				
			||||||
 | 
					                      "mortgage", | 
				
			||||||
 | 
					                      "inc2mort", | 
				
			||||||
 | 
					                      "mortgage_value_check", | 
				
			||||||
 | 
					                      "hb", | 
				
			||||||
 | 
					                      "savings_value_check", | 
				
			||||||
 | 
					                      "deposit_value_check", | 
				
			||||||
 | 
					                      "frombeds", | 
				
			||||||
 | 
					                      "staircase", | 
				
			||||||
 | 
					                      "stairbought", | 
				
			||||||
 | 
					                      "stairowned", | 
				
			||||||
 | 
					                      "mrent", | 
				
			||||||
 | 
					                      "exdate", | 
				
			||||||
 | 
					                      "exday", | 
				
			||||||
 | 
					                      "exmonth", | 
				
			||||||
 | 
					                      "exyear", | 
				
			||||||
 | 
					                      "resale", | 
				
			||||||
 | 
					                      "deposit", | 
				
			||||||
 | 
					                      "cashdis", | 
				
			||||||
 | 
					                      "disabled", | 
				
			||||||
 | 
					                      "lanomagr", | 
				
			||||||
 | 
					                      "wheel_value_check", | 
				
			||||||
 | 
					                      "soctenant", | 
				
			||||||
 | 
					                      "value", | 
				
			||||||
 | 
					                      "equity", | 
				
			||||||
 | 
					                      "discount", | 
				
			||||||
 | 
					                      "grant", | 
				
			||||||
 | 
					                      "pregyrha", | 
				
			||||||
 | 
					                      "pregla", | 
				
			||||||
 | 
					                      "pregghb", | 
				
			||||||
 | 
					                      "pregother", | 
				
			||||||
 | 
					                      "ppostcode_full", | 
				
			||||||
 | 
					                      "is_previous_la_inferred", | 
				
			||||||
 | 
					                      "ppcodenk", | 
				
			||||||
 | 
					                      "ppostc1", | 
				
			||||||
 | 
					                      "ppostc2", | 
				
			||||||
 | 
					                      "prevloc", | 
				
			||||||
 | 
					                      "previous_la_known", | 
				
			||||||
 | 
					                      "hhregres", | 
				
			||||||
 | 
					                      "hhregresstill", | 
				
			||||||
 | 
					                      "proplen", | 
				
			||||||
 | 
					                      "has_mscharge", | 
				
			||||||
 | 
					                      "mscharge", | 
				
			||||||
 | 
					                      "prevten", | 
				
			||||||
 | 
					                      "mortgageused", | 
				
			||||||
 | 
					                      "wchair", | 
				
			||||||
 | 
					                      "income2_value_check", | 
				
			||||||
 | 
					                      "armedforcesspouse", | 
				
			||||||
 | 
					                      "hodate", | 
				
			||||||
 | 
					                      "hoday", | 
				
			||||||
 | 
					                      "homonth", | 
				
			||||||
 | 
					                      "hoyear", | 
				
			||||||
 | 
					                      "fromprop", | 
				
			||||||
 | 
					                      "socprevten", | 
				
			||||||
 | 
					                      "mortgagelender", | 
				
			||||||
 | 
					                      "mortgagelenderother", | 
				
			||||||
 | 
					                      "mortlen", | 
				
			||||||
 | 
					                      "extrabor", | 
				
			||||||
 | 
					                      "hhmemb", | 
				
			||||||
 | 
					                      "totadult", | 
				
			||||||
 | 
					                      "totchild", | 
				
			||||||
 | 
					                      "hhtype", | 
				
			||||||
 | 
					                      "pcode1", | 
				
			||||||
 | 
					                      "pcode2", | 
				
			||||||
 | 
					                      "pcodenk", | 
				
			||||||
 | 
					                      "postcode_full", | 
				
			||||||
 | 
					                      "is_la_inferred", | 
				
			||||||
 | 
					                      "bulk_upload_id", | 
				
			||||||
 | 
					                      "retirement_value_check", | 
				
			||||||
 | 
					                      "hodate_check", | 
				
			||||||
 | 
					                      "extrabor_value_check", | 
				
			||||||
 | 
					                      "deposit_and_mortgage_value_check", | 
				
			||||||
 | 
					                      "shared_ownership_deposit_value_check", | 
				
			||||||
 | 
					                      "grant_value_check", | 
				
			||||||
 | 
					                      "value_value_check", | 
				
			||||||
 | 
					                      "old_persons_shared_ownership_value_check", | 
				
			||||||
 | 
					                      "staircase_bought_value_check", | 
				
			||||||
 | 
					                      "monthly_charges_value_check", | 
				
			||||||
 | 
					                      "details_known_5", | 
				
			||||||
 | 
					                      "details_known_6", | 
				
			||||||
 | 
					                      "saledate_check", | 
				
			||||||
 | 
					                      "prevshared", | 
				
			||||||
 | 
					                      "staircasesale", | 
				
			||||||
 | 
					                      "ethnic_group2", | 
				
			||||||
 | 
					                      "ethnicbuy2", | 
				
			||||||
 | 
					                      "proplen_asked", | 
				
			||||||
 | 
					                      "old_id", | 
				
			||||||
 | 
					                      "buy2living", | 
				
			||||||
 | 
					                      "prevtenbuy2", | 
				
			||||||
 | 
					                      "pregblank", | 
				
			||||||
 | 
					                      "uprn", | 
				
			||||||
 | 
					                      "uprn_known", | 
				
			||||||
 | 
					                      "uprn_confirmed", | 
				
			||||||
 | 
					                      "address_line1", | 
				
			||||||
 | 
					                      "address_line2", | 
				
			||||||
 | 
					                      "town_or_city", | 
				
			||||||
 | 
					                      "county", | 
				
			||||||
 | 
					                      "nationalbuy2", | 
				
			||||||
 | 
					                      "discounted_sale_value_check", | 
				
			||||||
 | 
					                      "student_not_child_value_check", | 
				
			||||||
 | 
					                      "percentage_discount_value_check", | 
				
			||||||
 | 
					                      "combined_income_value_check", | 
				
			||||||
 | 
					                      "buyer_livein_value_check", | 
				
			||||||
 | 
					                      "discarded_at", | 
				
			||||||
 | 
					                      "stairowned_value_check", | 
				
			||||||
 | 
					                      "creation_method", | 
				
			||||||
 | 
					                      "old_form_id", | 
				
			||||||
 | 
					                      "managing_organisation_id", | 
				
			||||||
 | 
					                      "duplicate_set_id", | 
				
			||||||
 | 
					                      "nationality_all", | 
				
			||||||
 | 
					                      "nationality_all_group", | 
				
			||||||
 | 
					                      "nationality_all_buyer2", | 
				
			||||||
 | 
					                      "nationality_all_buyer2_group", | 
				
			||||||
 | 
					                      "address_line1_input", | 
				
			||||||
 | 
					                      "postcode_full_input", | 
				
			||||||
 | 
					                      "address_search_value_check", | 
				
			||||||
 | 
					                      "uprn_selection", | 
				
			||||||
 | 
					                      "address_line1_as_entered", | 
				
			||||||
 | 
					                      "address_line2_as_entered", | 
				
			||||||
 | 
					                      "town_or_city_as_entered", | 
				
			||||||
 | 
					                      "county_as_entered", | 
				
			||||||
 | 
					                      "postcode_full_as_entered", | 
				
			||||||
 | 
					                      "la_as_entered", | 
				
			||||||
 | 
					                      "partner_under_16_value_check", | 
				
			||||||
 | 
					                      "multiple_partners_value_check", | 
				
			||||||
 | 
					                      "created_by_id", | 
				
			||||||
 | 
					                      "has_management_fee", | 
				
			||||||
 | 
					                      "management_fee", | 
				
			||||||
 | 
					                      "firststair", | 
				
			||||||
 | 
					                      "numstair", | 
				
			||||||
 | 
					                      "mrentprestaircasing", | 
				
			||||||
 | 
					                      "lasttransaction", | 
				
			||||||
 | 
					                      "initialpurchase"] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  (1..6).each do |index| | 
				
			||||||
 | 
					    EXPORT_FIELDS << "age#{index}" | 
				
			||||||
 | 
					    EXPORT_FIELDS << "age#{index}_known" | 
				
			||||||
 | 
					    EXPORT_FIELDS << "ecstat#{index}" | 
				
			||||||
 | 
					    EXPORT_FIELDS << "sex#{index}" | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  (2..6).each do |index| | 
				
			||||||
 | 
					    EXPORT_FIELDS << "relat#{index}" | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					end | 
				
			||||||
@ -1,5 +1,77 @@ | 
				
			|||||||
module Exports | 
					module Exports | 
				
			||||||
  class SalesLogExportService < Exports::XmlExportService | 
					  class SalesLogExportService < Exports::XmlExportService | 
				
			||||||
    def export_xml_sales_logs(full_update: false, collection_year: nil); end | 
					    include Exports::SalesLogExportConstants | 
				
			||||||
 | 
					    include CollectionTimeHelper | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def export_xml_sales_logs(full_update: false, collection_year: nil) | 
				
			||||||
 | 
					      archives_for_manifest = {} | 
				
			||||||
 | 
					      collection_years_to_export(collection_year).each do |year| | 
				
			||||||
 | 
					        recent_export = Export.sales.where(year:).order("started_at").last | 
				
			||||||
 | 
					        base_number = Export.sales.where(empty_export: false, year:).maximum(:base_number) || 1 | 
				
			||||||
 | 
					        export = build_export_run("sales", base_number, full_update, year) | 
				
			||||||
 | 
					        archives = write_export_archive(export, year, recent_export, full_update) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        archives_for_manifest.merge!(archives) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        export.empty_export = archives.empty? | 
				
			||||||
 | 
					        export.save! | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      archives_for_manifest | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_archive_name(year, base_number, increment) | 
				
			||||||
 | 
					      return unless year | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      base_number_str = "f#{base_number.to_s.rjust(4, '0')}" | 
				
			||||||
 | 
					      increment_str = "inc#{increment.to_s.rjust(4, '0')}" | 
				
			||||||
 | 
					      "core_sales_#{year}_#{year + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def retrieve_resources(recent_export, full_update, year) | 
				
			||||||
 | 
					      if !full_update && recent_export | 
				
			||||||
 | 
					        params = { from: recent_export.started_at, to: @start_time } | 
				
			||||||
 | 
					        SalesLog.exportable.where("(updated_at >= :from AND updated_at <= :to) OR (values_updated_at IS NOT NULL AND values_updated_at >= :from AND values_updated_at <= :to)", params).filter_by_year(year) | 
				
			||||||
 | 
					      else | 
				
			||||||
 | 
					        params = { to: @start_time } | 
				
			||||||
 | 
					        SalesLog.exportable.where("updated_at <= :to", params).filter_by_year(year) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def apply_cds_transformation(sales_log, _export_mode) | 
				
			||||||
 | 
					      sales_log.attributes_before_type_cast | 
				
			||||||
 | 
					      # attribute_hash["formid"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET) | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_omitted_field?(field_name, _sales_log) | 
				
			||||||
 | 
					      !EXPORT_FIELDS.include?(field_name) | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_export_xml(sales_logs) | 
				
			||||||
 | 
					      doc = Nokogiri::XML("<forms/>") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      sales_logs.each do |sales_log| | 
				
			||||||
 | 
					        attribute_hash = apply_cds_transformation(sales_log, EXPORT_MODE[:xml]) | 
				
			||||||
 | 
					        form = doc.create_element("form") | 
				
			||||||
 | 
					        doc.at("forms") << form | 
				
			||||||
 | 
					        attribute_hash.each do |key, value| | 
				
			||||||
 | 
					          if is_omitted_field?(key, sales_log) | 
				
			||||||
 | 
					            next | 
				
			||||||
 | 
					          else | 
				
			||||||
 | 
					            form << doc.create_element(key, value) | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      xml_doc_to_temp_file(doc) | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def collection_years_to_export(collection_year) | 
				
			||||||
 | 
					      return [collection_year] if collection_year.present? | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      FormHandler.instance.sales_forms.values.map { |f| f.start_date.year }.uniq.select { |year| year > 2024 } | 
				
			||||||
 | 
					    end | 
				
			||||||
  end | 
					  end | 
				
			||||||
end | 
					end | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,207 @@ | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?> | 
				
			||||||
 | 
					<forms> | 
				
			||||||
 | 
					  <form> | 
				
			||||||
 | 
					    <id>{id}</id> | 
				
			||||||
 | 
					    <status>1</status> | 
				
			||||||
 | 
					    <saledate>2026-03-01 00:00:00 UTC</saledate> | 
				
			||||||
 | 
					    <created_at>2026-03-01 00:00:00 UTC</created_at> | 
				
			||||||
 | 
					    <updated_at>2026-03-01 00:00:00 UTC</updated_at> | 
				
			||||||
 | 
					    <owning_organisation_id>{owning_org_id}</owning_organisation_id> | 
				
			||||||
 | 
					    <assigned_to_id>{assigned_to_id}</assigned_to_id> | 
				
			||||||
 | 
					    <purchid>123</purchid> | 
				
			||||||
 | 
					    <type>8</type> | 
				
			||||||
 | 
					    <ownershipsch>2</ownershipsch> | 
				
			||||||
 | 
					    <othtype/> | 
				
			||||||
 | 
					    <jointmore>1</jointmore> | 
				
			||||||
 | 
					    <jointpur>1</jointpur> | 
				
			||||||
 | 
					    <beds>2</beds> | 
				
			||||||
 | 
					    <companybuy/> | 
				
			||||||
 | 
					    <age1>27</age1> | 
				
			||||||
 | 
					    <age1_known>0</age1_known> | 
				
			||||||
 | 
					    <sex1>F</sex1> | 
				
			||||||
 | 
					    <ethnic>17</ethnic> | 
				
			||||||
 | 
					    <ethnic_group>17</ethnic_group> | 
				
			||||||
 | 
					    <buy1livein>1</buy1livein> | 
				
			||||||
 | 
					    <buylivein/> | 
				
			||||||
 | 
					    <builtype>1</builtype> | 
				
			||||||
 | 
					    <proptype>1</proptype> | 
				
			||||||
 | 
					    <age2>33</age2> | 
				
			||||||
 | 
					    <age2_known>0</age2_known> | 
				
			||||||
 | 
					    <relat2>P</relat2> | 
				
			||||||
 | 
					    <sex2>X</sex2> | 
				
			||||||
 | 
					    <noint>2</noint> | 
				
			||||||
 | 
					    <buy2livein>1</buy2livein> | 
				
			||||||
 | 
					    <ecstat2>1</ecstat2> | 
				
			||||||
 | 
					    <privacynotice>1</privacynotice> | 
				
			||||||
 | 
					    <ecstat1>1</ecstat1> | 
				
			||||||
 | 
					    <wheel>1</wheel> | 
				
			||||||
 | 
					    <hholdcount>4</hholdcount> | 
				
			||||||
 | 
					    <age3>14</age3> | 
				
			||||||
 | 
					    <age3_known>0</age3_known> | 
				
			||||||
 | 
					    <la>E09000033</la> | 
				
			||||||
 | 
					    <la_known>1</la_known> | 
				
			||||||
 | 
					    <income1>10000</income1> | 
				
			||||||
 | 
					    <income1nk>0</income1nk> | 
				
			||||||
 | 
					    <details_known_2/> | 
				
			||||||
 | 
					    <details_known_3>1</details_known_3> | 
				
			||||||
 | 
					    <details_known_4>1</details_known_4> | 
				
			||||||
 | 
					    <age4>18</age4> | 
				
			||||||
 | 
					    <age4_known>0</age4_known> | 
				
			||||||
 | 
					    <age5>40</age5> | 
				
			||||||
 | 
					    <age5_known>0</age5_known> | 
				
			||||||
 | 
					    <age6>40</age6> | 
				
			||||||
 | 
					    <age6_known>0</age6_known> | 
				
			||||||
 | 
					    <inc1mort>1</inc1mort> | 
				
			||||||
 | 
					    <income2>10000</income2> | 
				
			||||||
 | 
					    <income2nk>0</income2nk> | 
				
			||||||
 | 
					    <savingsnk>1</savingsnk> | 
				
			||||||
 | 
					    <savings/> | 
				
			||||||
 | 
					    <prevown>1</prevown> | 
				
			||||||
 | 
					    <sex3>F</sex3> | 
				
			||||||
 | 
					    <updated_by_id/> | 
				
			||||||
 | 
					    <income1_value_check/> | 
				
			||||||
 | 
					    <mortgage>20000.0</mortgage> | 
				
			||||||
 | 
					    <inc2mort>1</inc2mort> | 
				
			||||||
 | 
					    <mortgage_value_check/> | 
				
			||||||
 | 
					    <ecstat3>9</ecstat3> | 
				
			||||||
 | 
					    <ecstat4>3</ecstat4> | 
				
			||||||
 | 
					    <ecstat5>2</ecstat5> | 
				
			||||||
 | 
					    <ecstat6>1</ecstat6> | 
				
			||||||
 | 
					    <relat3>X</relat3> | 
				
			||||||
 | 
					    <relat4>X</relat4> | 
				
			||||||
 | 
					    <relat5>R</relat5> | 
				
			||||||
 | 
					    <relat6>R</relat6> | 
				
			||||||
 | 
					    <hb>4</hb> | 
				
			||||||
 | 
					    <sex4>X</sex4> | 
				
			||||||
 | 
					    <sex5>M</sex5> | 
				
			||||||
 | 
					    <sex6>X</sex6> | 
				
			||||||
 | 
					    <savings_value_check/> | 
				
			||||||
 | 
					    <deposit_value_check/> | 
				
			||||||
 | 
					    <frombeds/> | 
				
			||||||
 | 
					    <staircase/> | 
				
			||||||
 | 
					    <stairbought/> | 
				
			||||||
 | 
					    <stairowned/> | 
				
			||||||
 | 
					    <mrent/> | 
				
			||||||
 | 
					    <exdate/> | 
				
			||||||
 | 
					    <exday/> | 
				
			||||||
 | 
					    <exmonth/> | 
				
			||||||
 | 
					    <exyear/> | 
				
			||||||
 | 
					    <resale/> | 
				
			||||||
 | 
					    <deposit>80000.0</deposit> | 
				
			||||||
 | 
					    <cashdis/> | 
				
			||||||
 | 
					    <disabled>1</disabled> | 
				
			||||||
 | 
					    <lanomagr/> | 
				
			||||||
 | 
					    <wheel_value_check/> | 
				
			||||||
 | 
					    <soctenant/> | 
				
			||||||
 | 
					    <value>110000.0</value> | 
				
			||||||
 | 
					    <equity/> | 
				
			||||||
 | 
					    <discount/> | 
				
			||||||
 | 
					    <grant>10000.0</grant> | 
				
			||||||
 | 
					    <pregyrha>1</pregyrha> | 
				
			||||||
 | 
					    <pregla>1</pregla> | 
				
			||||||
 | 
					    <pregghb>1</pregghb> | 
				
			||||||
 | 
					    <pregother>1</pregother> | 
				
			||||||
 | 
					    <ppostcode_full>SW1A 1AA</ppostcode_full> | 
				
			||||||
 | 
					    <is_previous_la_inferred>true</is_previous_la_inferred> | 
				
			||||||
 | 
					    <ppcodenk>0</ppcodenk> | 
				
			||||||
 | 
					    <ppostc1>SW1A</ppostc1> | 
				
			||||||
 | 
					    <ppostc2>1AA</ppostc2> | 
				
			||||||
 | 
					    <prevloc>E09000033</prevloc> | 
				
			||||||
 | 
					    <previous_la_known>1</previous_la_known> | 
				
			||||||
 | 
					    <hhregres>7</hhregres> | 
				
			||||||
 | 
					    <hhregresstill/> | 
				
			||||||
 | 
					    <proplen/> | 
				
			||||||
 | 
					    <has_mscharge>1</has_mscharge> | 
				
			||||||
 | 
					    <mscharge>100.0</mscharge> | 
				
			||||||
 | 
					    <prevten>1</prevten> | 
				
			||||||
 | 
					    <mortgageused>1</mortgageused> | 
				
			||||||
 | 
					    <wchair>1</wchair> | 
				
			||||||
 | 
					    <income2_value_check/> | 
				
			||||||
 | 
					    <armedforcesspouse>5</armedforcesspouse> | 
				
			||||||
 | 
					    <hodate/> | 
				
			||||||
 | 
					    <hoday/> | 
				
			||||||
 | 
					    <homonth/> | 
				
			||||||
 | 
					    <hoyear/> | 
				
			||||||
 | 
					    <fromprop/> | 
				
			||||||
 | 
					    <socprevten/> | 
				
			||||||
 | 
					    <mortgagelender>5</mortgagelender> | 
				
			||||||
 | 
					    <mortgagelenderother/> | 
				
			||||||
 | 
					    <mortlen>10</mortlen> | 
				
			||||||
 | 
					    <extrabor>1</extrabor> | 
				
			||||||
 | 
					    <hhmemb>6</hhmemb> | 
				
			||||||
 | 
					    <totadult>5</totadult> | 
				
			||||||
 | 
					    <totchild>1</totchild> | 
				
			||||||
 | 
					    <hhtype>6</hhtype> | 
				
			||||||
 | 
					    <pcode1>SW1A</pcode1> | 
				
			||||||
 | 
					    <pcode2>1AA</pcode2> | 
				
			||||||
 | 
					    <pcodenk>0</pcodenk> | 
				
			||||||
 | 
					    <postcode_full>AA1 1AA</postcode_full> | 
				
			||||||
 | 
					    <is_la_inferred>true</is_la_inferred> | 
				
			||||||
 | 
					    <bulk_upload_id/> | 
				
			||||||
 | 
					    <retirement_value_check/> | 
				
			||||||
 | 
					    <hodate_check/> | 
				
			||||||
 | 
					    <extrabor_value_check/> | 
				
			||||||
 | 
					    <deposit_and_mortgage_value_check/> | 
				
			||||||
 | 
					    <shared_ownership_deposit_value_check/> | 
				
			||||||
 | 
					    <grant_value_check/> | 
				
			||||||
 | 
					    <value_value_check/> | 
				
			||||||
 | 
					    <old_persons_shared_ownership_value_check/> | 
				
			||||||
 | 
					    <staircase_bought_value_check/> | 
				
			||||||
 | 
					    <monthly_charges_value_check/> | 
				
			||||||
 | 
					    <details_known_5>1</details_known_5> | 
				
			||||||
 | 
					    <details_known_6>1</details_known_6> | 
				
			||||||
 | 
					    <saledate_check/> | 
				
			||||||
 | 
					    <prevshared>2</prevshared> | 
				
			||||||
 | 
					    <staircasesale/> | 
				
			||||||
 | 
					    <ethnic_group2>17</ethnic_group2> | 
				
			||||||
 | 
					    <ethnicbuy2/> | 
				
			||||||
 | 
					    <proplen_asked/> | 
				
			||||||
 | 
					    <old_id/> | 
				
			||||||
 | 
					    <buy2living>3</buy2living> | 
				
			||||||
 | 
					    <prevtenbuy2/> | 
				
			||||||
 | 
					    <pregblank/> | 
				
			||||||
 | 
					    <uprn>1</uprn> | 
				
			||||||
 | 
					    <uprn_known>1</uprn_known> | 
				
			||||||
 | 
					    <uprn_confirmed>1</uprn_confirmed> | 
				
			||||||
 | 
					    <address_line1>1, Test Street</address_line1> | 
				
			||||||
 | 
					    <address_line2/> | 
				
			||||||
 | 
					    <town_or_city>Test Town</town_or_city> | 
				
			||||||
 | 
					    <county/> | 
				
			||||||
 | 
					    <nationalbuy2>13</nationalbuy2> | 
				
			||||||
 | 
					    <discounted_sale_value_check/> | 
				
			||||||
 | 
					    <student_not_child_value_check/> | 
				
			||||||
 | 
					    <percentage_discount_value_check/> | 
				
			||||||
 | 
					    <combined_income_value_check/> | 
				
			||||||
 | 
					    <buyer_livein_value_check/> | 
				
			||||||
 | 
					    <discarded_at/> | 
				
			||||||
 | 
					    <stairowned_value_check/> | 
				
			||||||
 | 
					    <creation_method>1</creation_method> | 
				
			||||||
 | 
					    <old_form_id/> | 
				
			||||||
 | 
					    <managing_organisation_id>{managing_org_id}</managing_organisation_id> | 
				
			||||||
 | 
					    <duplicate_set_id/> | 
				
			||||||
 | 
					    <nationality_all>826</nationality_all> | 
				
			||||||
 | 
					    <nationality_all_group>826</nationality_all_group> | 
				
			||||||
 | 
					    <nationality_all_buyer2>826</nationality_all_buyer2> | 
				
			||||||
 | 
					    <nationality_all_buyer2_group>826</nationality_all_buyer2_group> | 
				
			||||||
 | 
					    <address_line1_input>Address line 1</address_line1_input> | 
				
			||||||
 | 
					    <postcode_full_input>SW1A 1AA</postcode_full_input> | 
				
			||||||
 | 
					    <address_search_value_check/> | 
				
			||||||
 | 
					    <uprn_selection/> | 
				
			||||||
 | 
					    <address_line1_as_entered/> | 
				
			||||||
 | 
					    <address_line2_as_entered/> | 
				
			||||||
 | 
					    <town_or_city_as_entered/> | 
				
			||||||
 | 
					    <county_as_entered/> | 
				
			||||||
 | 
					    <postcode_full_as_entered/> | 
				
			||||||
 | 
					    <la_as_entered/> | 
				
			||||||
 | 
					    <partner_under_16_value_check/> | 
				
			||||||
 | 
					    <multiple_partners_value_check/> | 
				
			||||||
 | 
					    <created_by_id>{created_by_id}</created_by_id> | 
				
			||||||
 | 
					    <has_management_fee/> | 
				
			||||||
 | 
					    <management_fee/> | 
				
			||||||
 | 
					    <firststair/> | 
				
			||||||
 | 
					    <numstair/> | 
				
			||||||
 | 
					    <mrentprestaircasing/> | 
				
			||||||
 | 
					    <lasttransaction/> | 
				
			||||||
 | 
					    <initialpurchase/> | 
				
			||||||
 | 
					  </form> | 
				
			||||||
 | 
					</forms> | 
				
			||||||
@ -0,0 +1,207 @@ | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?> | 
				
			||||||
 | 
					<forms> | 
				
			||||||
 | 
					  <form> | 
				
			||||||
 | 
					    <id>{id}</id> | 
				
			||||||
 | 
					    <status>2</status> | 
				
			||||||
 | 
					    <saledate>2024-04-02 23:00:00 UTC</saledate> | 
				
			||||||
 | 
					    <created_at>2024-04-02 23:00:00 UTC</created_at> | 
				
			||||||
 | 
					    <updated_at>2024-04-02 23:00:00 UTC</updated_at> | 
				
			||||||
 | 
					    <owning_organisation_id>{owning_org_id}</owning_organisation_id> | 
				
			||||||
 | 
					    <assigned_to_id>{assigned_to_id}</assigned_to_id> | 
				
			||||||
 | 
					    <purchid>123</purchid> | 
				
			||||||
 | 
					    <type>8</type> | 
				
			||||||
 | 
					    <ownershipsch>2</ownershipsch> | 
				
			||||||
 | 
					    <othtype/> | 
				
			||||||
 | 
					    <jointmore>1</jointmore> | 
				
			||||||
 | 
					    <jointpur>1</jointpur> | 
				
			||||||
 | 
					    <beds>2</beds> | 
				
			||||||
 | 
					    <companybuy/> | 
				
			||||||
 | 
					    <age1>27</age1> | 
				
			||||||
 | 
					    <age1_known>0</age1_known> | 
				
			||||||
 | 
					    <sex1>F</sex1> | 
				
			||||||
 | 
					    <ethnic>17</ethnic> | 
				
			||||||
 | 
					    <ethnic_group>17</ethnic_group> | 
				
			||||||
 | 
					    <buy1livein>1</buy1livein> | 
				
			||||||
 | 
					    <buylivein/> | 
				
			||||||
 | 
					    <builtype>1</builtype> | 
				
			||||||
 | 
					    <proptype>1</proptype> | 
				
			||||||
 | 
					    <age2>33</age2> | 
				
			||||||
 | 
					    <age2_known>0</age2_known> | 
				
			||||||
 | 
					    <relat2>P</relat2> | 
				
			||||||
 | 
					    <sex2>X</sex2> | 
				
			||||||
 | 
					    <noint>2</noint> | 
				
			||||||
 | 
					    <buy2livein>1</buy2livein> | 
				
			||||||
 | 
					    <ecstat2>1</ecstat2> | 
				
			||||||
 | 
					    <privacynotice>1</privacynotice> | 
				
			||||||
 | 
					    <ecstat1>1</ecstat1> | 
				
			||||||
 | 
					    <wheel>1</wheel> | 
				
			||||||
 | 
					    <hholdcount>4</hholdcount> | 
				
			||||||
 | 
					    <age3>14</age3> | 
				
			||||||
 | 
					    <age3_known>0</age3_known> | 
				
			||||||
 | 
					    <la>E09000033</la> | 
				
			||||||
 | 
					    <la_known>1</la_known> | 
				
			||||||
 | 
					    <income1>10000</income1> | 
				
			||||||
 | 
					    <income1nk>0</income1nk> | 
				
			||||||
 | 
					    <details_known_2/> | 
				
			||||||
 | 
					    <details_known_3>1</details_known_3> | 
				
			||||||
 | 
					    <details_known_4>1</details_known_4> | 
				
			||||||
 | 
					    <age4>18</age4> | 
				
			||||||
 | 
					    <age4_known>0</age4_known> | 
				
			||||||
 | 
					    <age5>40</age5> | 
				
			||||||
 | 
					    <age5_known>0</age5_known> | 
				
			||||||
 | 
					    <age6>40</age6> | 
				
			||||||
 | 
					    <age6_known>0</age6_known> | 
				
			||||||
 | 
					    <inc1mort>1</inc1mort> | 
				
			||||||
 | 
					    <income2>10000</income2> | 
				
			||||||
 | 
					    <income2nk>0</income2nk> | 
				
			||||||
 | 
					    <savingsnk>1</savingsnk> | 
				
			||||||
 | 
					    <savings/> | 
				
			||||||
 | 
					    <prevown>1</prevown> | 
				
			||||||
 | 
					    <sex3>F</sex3> | 
				
			||||||
 | 
					    <updated_by_id/> | 
				
			||||||
 | 
					    <income1_value_check/> | 
				
			||||||
 | 
					    <mortgage>20000.0</mortgage> | 
				
			||||||
 | 
					    <inc2mort>1</inc2mort> | 
				
			||||||
 | 
					    <mortgage_value_check/> | 
				
			||||||
 | 
					    <ecstat3>9</ecstat3> | 
				
			||||||
 | 
					    <ecstat4>3</ecstat4> | 
				
			||||||
 | 
					    <ecstat5>2</ecstat5> | 
				
			||||||
 | 
					    <ecstat6>1</ecstat6> | 
				
			||||||
 | 
					    <relat3>X</relat3> | 
				
			||||||
 | 
					    <relat4>X</relat4> | 
				
			||||||
 | 
					    <relat5>R</relat5> | 
				
			||||||
 | 
					    <relat6>R</relat6> | 
				
			||||||
 | 
					    <hb>4</hb> | 
				
			||||||
 | 
					    <sex4>X</sex4> | 
				
			||||||
 | 
					    <sex5>M</sex5> | 
				
			||||||
 | 
					    <sex6>X</sex6> | 
				
			||||||
 | 
					    <savings_value_check/> | 
				
			||||||
 | 
					    <deposit_value_check/> | 
				
			||||||
 | 
					    <frombeds/> | 
				
			||||||
 | 
					    <staircase/> | 
				
			||||||
 | 
					    <stairbought/> | 
				
			||||||
 | 
					    <stairowned/> | 
				
			||||||
 | 
					    <mrent/> | 
				
			||||||
 | 
					    <exdate/> | 
				
			||||||
 | 
					    <exday/> | 
				
			||||||
 | 
					    <exmonth/> | 
				
			||||||
 | 
					    <exyear/> | 
				
			||||||
 | 
					    <resale/> | 
				
			||||||
 | 
					    <deposit>80000.0</deposit> | 
				
			||||||
 | 
					    <cashdis/> | 
				
			||||||
 | 
					    <disabled>1</disabled> | 
				
			||||||
 | 
					    <lanomagr/> | 
				
			||||||
 | 
					    <wheel_value_check/> | 
				
			||||||
 | 
					    <soctenant/> | 
				
			||||||
 | 
					    <value>110000.0</value> | 
				
			||||||
 | 
					    <equity/> | 
				
			||||||
 | 
					    <discount/> | 
				
			||||||
 | 
					    <grant>10000.0</grant> | 
				
			||||||
 | 
					    <pregyrha>1</pregyrha> | 
				
			||||||
 | 
					    <pregla>1</pregla> | 
				
			||||||
 | 
					    <pregghb>1</pregghb> | 
				
			||||||
 | 
					    <pregother>1</pregother> | 
				
			||||||
 | 
					    <ppostcode_full>SW1A 1AA</ppostcode_full> | 
				
			||||||
 | 
					    <is_previous_la_inferred>true</is_previous_la_inferred> | 
				
			||||||
 | 
					    <ppcodenk>0</ppcodenk> | 
				
			||||||
 | 
					    <ppostc1>SW1A</ppostc1> | 
				
			||||||
 | 
					    <ppostc2>1AA</ppostc2> | 
				
			||||||
 | 
					    <prevloc>E09000033</prevloc> | 
				
			||||||
 | 
					    <previous_la_known>1</previous_la_known> | 
				
			||||||
 | 
					    <hhregres>7</hhregres> | 
				
			||||||
 | 
					    <hhregresstill/> | 
				
			||||||
 | 
					    <proplen>10</proplen> | 
				
			||||||
 | 
					    <has_mscharge>1</has_mscharge> | 
				
			||||||
 | 
					    <mscharge>100.0</mscharge> | 
				
			||||||
 | 
					    <prevten>1</prevten> | 
				
			||||||
 | 
					    <mortgageused>1</mortgageused> | 
				
			||||||
 | 
					    <wchair>1</wchair> | 
				
			||||||
 | 
					    <income2_value_check/> | 
				
			||||||
 | 
					    <armedforcesspouse>5</armedforcesspouse> | 
				
			||||||
 | 
					    <hodate/> | 
				
			||||||
 | 
					    <hoday/> | 
				
			||||||
 | 
					    <homonth/> | 
				
			||||||
 | 
					    <hoyear/> | 
				
			||||||
 | 
					    <fromprop/> | 
				
			||||||
 | 
					    <socprevten/> | 
				
			||||||
 | 
					    <mortgagelender>5</mortgagelender> | 
				
			||||||
 | 
					    <mortgagelenderother/> | 
				
			||||||
 | 
					    <mortlen>10</mortlen> | 
				
			||||||
 | 
					    <extrabor>1</extrabor> | 
				
			||||||
 | 
					    <hhmemb>6</hhmemb> | 
				
			||||||
 | 
					    <totadult>5</totadult> | 
				
			||||||
 | 
					    <totchild>1</totchild> | 
				
			||||||
 | 
					    <hhtype>6</hhtype> | 
				
			||||||
 | 
					    <pcode1>SW1A</pcode1> | 
				
			||||||
 | 
					    <pcode2>1AA</pcode2> | 
				
			||||||
 | 
					    <pcodenk>0</pcodenk> | 
				
			||||||
 | 
					    <postcode_full>AA1 1AA</postcode_full> | 
				
			||||||
 | 
					    <is_la_inferred>true</is_la_inferred> | 
				
			||||||
 | 
					    <bulk_upload_id/> | 
				
			||||||
 | 
					    <retirement_value_check/> | 
				
			||||||
 | 
					    <hodate_check/> | 
				
			||||||
 | 
					    <extrabor_value_check/> | 
				
			||||||
 | 
					    <deposit_and_mortgage_value_check/> | 
				
			||||||
 | 
					    <shared_ownership_deposit_value_check/> | 
				
			||||||
 | 
					    <grant_value_check/> | 
				
			||||||
 | 
					    <value_value_check/> | 
				
			||||||
 | 
					    <old_persons_shared_ownership_value_check/> | 
				
			||||||
 | 
					    <staircase_bought_value_check/> | 
				
			||||||
 | 
					    <monthly_charges_value_check/> | 
				
			||||||
 | 
					    <details_known_5>1</details_known_5> | 
				
			||||||
 | 
					    <details_known_6>1</details_known_6> | 
				
			||||||
 | 
					    <saledate_check/> | 
				
			||||||
 | 
					    <prevshared>2</prevshared> | 
				
			||||||
 | 
					    <staircasesale/> | 
				
			||||||
 | 
					    <ethnic_group2>17</ethnic_group2> | 
				
			||||||
 | 
					    <ethnicbuy2/> | 
				
			||||||
 | 
					    <proplen_asked>1</proplen_asked> | 
				
			||||||
 | 
					    <old_id/> | 
				
			||||||
 | 
					    <buy2living>3</buy2living> | 
				
			||||||
 | 
					    <prevtenbuy2/> | 
				
			||||||
 | 
					    <pregblank/> | 
				
			||||||
 | 
					    <uprn>1</uprn> | 
				
			||||||
 | 
					    <uprn_known>1</uprn_known> | 
				
			||||||
 | 
					    <uprn_confirmed>1</uprn_confirmed> | 
				
			||||||
 | 
					    <address_line1>1, Test Street</address_line1> | 
				
			||||||
 | 
					    <address_line2/> | 
				
			||||||
 | 
					    <town_or_city>Test Town</town_or_city> | 
				
			||||||
 | 
					    <county/> | 
				
			||||||
 | 
					    <nationalbuy2>13</nationalbuy2> | 
				
			||||||
 | 
					    <discounted_sale_value_check/> | 
				
			||||||
 | 
					    <student_not_child_value_check/> | 
				
			||||||
 | 
					    <percentage_discount_value_check/> | 
				
			||||||
 | 
					    <combined_income_value_check/> | 
				
			||||||
 | 
					    <buyer_livein_value_check/> | 
				
			||||||
 | 
					    <discarded_at/> | 
				
			||||||
 | 
					    <stairowned_value_check/> | 
				
			||||||
 | 
					    <creation_method>1</creation_method> | 
				
			||||||
 | 
					    <old_form_id/> | 
				
			||||||
 | 
					    <managing_organisation_id>{managing_org_id}</managing_organisation_id> | 
				
			||||||
 | 
					    <duplicate_set_id/> | 
				
			||||||
 | 
					    <nationality_all>826</nationality_all> | 
				
			||||||
 | 
					    <nationality_all_group>826</nationality_all_group> | 
				
			||||||
 | 
					    <nationality_all_buyer2>826</nationality_all_buyer2> | 
				
			||||||
 | 
					    <nationality_all_buyer2_group>826</nationality_all_buyer2_group> | 
				
			||||||
 | 
					    <address_line1_input>Address line 1</address_line1_input> | 
				
			||||||
 | 
					    <postcode_full_input>SW1A 1AA</postcode_full_input> | 
				
			||||||
 | 
					    <address_search_value_check/> | 
				
			||||||
 | 
					    <uprn_selection/> | 
				
			||||||
 | 
					    <address_line1_as_entered/> | 
				
			||||||
 | 
					    <address_line2_as_entered/> | 
				
			||||||
 | 
					    <town_or_city_as_entered/> | 
				
			||||||
 | 
					    <county_as_entered/> | 
				
			||||||
 | 
					    <postcode_full_as_entered/> | 
				
			||||||
 | 
					    <la_as_entered/> | 
				
			||||||
 | 
					    <partner_under_16_value_check/> | 
				
			||||||
 | 
					    <multiple_partners_value_check/> | 
				
			||||||
 | 
					    <created_by_id>{created_by_id}</created_by_id> | 
				
			||||||
 | 
					    <has_management_fee/> | 
				
			||||||
 | 
					    <management_fee/> | 
				
			||||||
 | 
					    <firststair/> | 
				
			||||||
 | 
					    <numstair/> | 
				
			||||||
 | 
					    <mrentprestaircasing/> | 
				
			||||||
 | 
					    <lasttransaction/> | 
				
			||||||
 | 
					    <initialpurchase/> | 
				
			||||||
 | 
					  </form> | 
				
			||||||
 | 
					</forms> | 
				
			||||||
@ -0,0 +1,363 @@ | 
				
			|||||||
 | 
					require "rails_helper" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe Exports::SalesLogExportService do | 
				
			||||||
 | 
					  subject(:export_service) { described_class.new(storage_service, start_time) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:storage_service) { instance_double(Storage::S3Service) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:xml_export_file) { File.open("spec/fixtures/exports/sales_log.xml", "r:UTF-8") } | 
				
			||||||
 | 
					  let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:expected_zip_filename) { "core_sales_2025_2026_apr_mar_f0001_inc0001.zip" } | 
				
			||||||
 | 
					  let(:expected_data_filename) { "core_sales_2025_2026_apr_mar_f0001_inc0001_pt001.xml" } | 
				
			||||||
 | 
					  let(:expected_manifest_filename) { "manifest.xml" } | 
				
			||||||
 | 
					  let(:start_time) { Time.zone.local(2026, 3, 1) } | 
				
			||||||
 | 
					  let(:organisation) { create(:organisation, name: "MHCLG", housing_registration_no: 1234) } | 
				
			||||||
 | 
					  let(:user) { FactoryBot.create(:user, email: "test1@example.com", organisation:) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def replace_entity_ids(sales_log, export_template) | 
				
			||||||
 | 
					    # export_template.sub!(/\{id\}/, (sales_log["id"] + Exports::SalesLogExportService::LOG_ID_OFFSET).to_s) | 
				
			||||||
 | 
					    export_template.sub!(/\{owning_org_id\}/, sales_log["owning_organisation_id"].to_s) | 
				
			||||||
 | 
					    export_template.sub!(/\{managing_org_id\}/, sales_log["managing_organisation_id"].to_s) | 
				
			||||||
 | 
					    export_template.sub!(/\{assigned_to_id\}/, sales_log["assigned_to_id"].to_s) | 
				
			||||||
 | 
					    export_template.sub!(/\{created_by_id\}/, sales_log["created_by_id"].to_s) | 
				
			||||||
 | 
					    export_template.sub!(/\{id\}/, sales_log["id"].to_s) | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def replace_record_number(export_template, record_number) | 
				
			||||||
 | 
					    export_template.sub!(/\{recno\}/, record_number.to_s) | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before do | 
				
			||||||
 | 
					    Timecop.freeze(start_time) | 
				
			||||||
 | 
					    Singleton.__init__(FormHandler) | 
				
			||||||
 | 
					    allow(storage_service).to receive(:write_file) | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  after do | 
				
			||||||
 | 
					    Timecop.return | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context "when exporting daily sales logs in XML" do | 
				
			||||||
 | 
					    context "and no sales logs are available for export" do | 
				
			||||||
 | 
					      it "returns an empty archives list" do | 
				
			||||||
 | 
					        expect(storage_service).not_to receive(:write_file) | 
				
			||||||
 | 
					        expect(export_service.export_xml_sales_logs).to eq({}) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "when one pending sales log exists" do | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        FactoryBot.create( | 
				
			||||||
 | 
					          :sales_log, | 
				
			||||||
 | 
					          :export, | 
				
			||||||
 | 
					          status: "pending", | 
				
			||||||
 | 
					          skip_update_status: true, | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns empty archives list for archives manifest" do | 
				
			||||||
 | 
					        expect(storage_service).not_to receive(:write_file) | 
				
			||||||
 | 
					        expect(export_service.export_xml_sales_logs).to eq({}) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and one sales log is available for export" do | 
				
			||||||
 | 
					      let!(:sales_log) { FactoryBot.create(:sales_log, :export, assigned_to: user) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates a ZIP export file with the expected filename" do | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML export file with the expected filename within the ZIP file" do | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.name).to eq(expected_data_filename) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML manifest file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					        expected_content = replace_record_number(local_manifest_file.read, 1) | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML export file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					        expected_content = replace_entity_ids(sales_log, xml_export_file.read) | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "returns the list with correct archive" do | 
				
			||||||
 | 
					        expect(export_service.export_xml_sales_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and multiple sales logs are available for export on different periods" do | 
				
			||||||
 | 
					      let(:previous_zip_filename) { "core_sales_2024_2025_apr_mar_f0001_inc0001.zip" } | 
				
			||||||
 | 
					      let(:next_zip_filename) { "core_sales_2026_2027_apr_mar_f0001_inc0001.zip" } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, :ignore_validation_errors, saledate: Time.zone.local(2024, 5, 1)) | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2025, 5, 1)) | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, :ignore_validation_errors, saledate: Time.zone.local(2026, 4, 1)) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "when sales logs are across multiple years" do | 
				
			||||||
 | 
					        it "generates multiple ZIP export files with the expected filenames" do | 
				
			||||||
 | 
					          expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) | 
				
			||||||
 | 
					          expect(storage_service).not_to receive(:write_file).with(previous_zip_filename, any_args) | 
				
			||||||
 | 
					          expect(storage_service).to receive(:write_file).with(next_zip_filename, any_args) | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Building export run for sales 2025") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Creating core_sales_2025_2026_apr_mar_f0001_inc0001 - 1 resources") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Added core_sales_2025_2026_apr_mar_f0001_inc0001_pt001.xml") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Writing core_sales_2025_2026_apr_mar_f0001_inc0001.zip") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Building export run for sales 2026") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Creating core_sales_2026_2027_apr_mar_f0001_inc0001 - 1 resources") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Added core_sales_2026_2027_apr_mar_f0001_inc0001_pt001.xml") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Writing core_sales_2026_2027_apr_mar_f0001_inc0001.zip") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "generates zip export files only for specified year" do | 
				
			||||||
 | 
					          expect(storage_service).to receive(:write_file).with(next_zip_filename, any_args) | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Building export run for sales 2026") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Creating core_sales_2026_2027_apr_mar_f0001_inc0001 - 1 resources") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Added core_sales_2026_2027_apr_mar_f0001_inc0001_pt001.xml") | 
				
			||||||
 | 
					          expect(Rails.logger).to receive(:info).with("Writing core_sales_2026_2027_apr_mar_f0001_inc0001.zip") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs(collection_year: 2026) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context "and previous full exports are different for previous years" do | 
				
			||||||
 | 
					          let(:expected_zip_filename) { "core_sales_2025_2026_apr_mar_f0007_inc0004.zip" } | 
				
			||||||
 | 
					          let(:next_zip_filename) { "core_sales_2026_2027_apr_mar_f0001_inc0001.zip" } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          before do | 
				
			||||||
 | 
					            Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: "sales", year: 2025).save! | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          it "generates multiple ZIP export files with different base numbers in the filenames" do | 
				
			||||||
 | 
					            expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) | 
				
			||||||
 | 
					            expect(storage_service).to receive(:write_file).with(next_zip_filename, any_args) | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Building export run for sales 2025") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Creating core_sales_2025_2026_apr_mar_f0007_inc0004 - 1 resources") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Added core_sales_2025_2026_apr_mar_f0007_inc0004_pt001.xml") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Writing core_sales_2025_2026_apr_mar_f0007_inc0004.zip") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Building export run for sales 2026") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Creating core_sales_2026_2027_apr_mar_f0001_inc0001 - 1 resources") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Added core_sales_2026_2027_apr_mar_f0001_inc0001_pt001.xml") | 
				
			||||||
 | 
					            expect(Rails.logger).to receive(:info).with("Writing core_sales_2026_2027_apr_mar_f0001_inc0001.zip") | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            export_service.export_xml_sales_logs | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and multiple sales logs are available for export on same quarter" do | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2025, 4, 1)) | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2025, 4, 20)) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML manifest file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					        expected_content = replace_record_number(local_manifest_file.read, 2) | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "creates a logs export record in a database with correct time" do | 
				
			||||||
 | 
					        expect { export_service.export_xml_sales_logs } | 
				
			||||||
 | 
					          .to change(Export, :count).by(2) | 
				
			||||||
 | 
					        expect(Export.last.started_at).to be_within(2.seconds).of(start_time) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "when this is the first export (full)" do | 
				
			||||||
 | 
					        it "returns a ZIP archive for the master manifest (existing sales logs)" do | 
				
			||||||
 | 
					          expect(export_service.export_xml_sales_logs).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time }) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "and underlying data changes between getting the logs and writting the manifest" do | 
				
			||||||
 | 
					        before do | 
				
			||||||
 | 
					          FactoryBot.create(:sales_log, saledate: Time.zone.local(2026, 2, 1)) | 
				
			||||||
 | 
					          FactoryBot.create(:sales_log, :ignore_validation_errors, saledate: Time.zone.local(2026, 4, 1)) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def remove_logs(logs) | 
				
			||||||
 | 
					          logs.each(&:destroy) | 
				
			||||||
 | 
					          file = Tempfile.new | 
				
			||||||
 | 
					          doc = Nokogiri::XML("<forms/>") | 
				
			||||||
 | 
					          doc.write_xml_to(file, encoding: "UTF-8") | 
				
			||||||
 | 
					          file.rewind | 
				
			||||||
 | 
					          file | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def create_fake_maifest | 
				
			||||||
 | 
					          file = Tempfile.new | 
				
			||||||
 | 
					          doc = Nokogiri::XML("<forms/>") | 
				
			||||||
 | 
					          doc.write_xml_to(file, encoding: "UTF-8") | 
				
			||||||
 | 
					          file.rewind | 
				
			||||||
 | 
					          file | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "maintains the same record number" do | 
				
			||||||
 | 
					          # rubocop:disable RSpec/SubjectStub | 
				
			||||||
 | 
					          allow(export_service).to receive(:build_export_xml) do |logs| | 
				
			||||||
 | 
					            remove_logs(logs) | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					          allow(export_service).to receive(:build_manifest_xml) do | 
				
			||||||
 | 
					            create_fake_maifest | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          expect(export_service).to receive(:build_manifest_xml).with(1) | 
				
			||||||
 | 
					          # rubocop:enable RSpec/SubjectStub | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "when this is a second export (partial)" do | 
				
			||||||
 | 
					        before do | 
				
			||||||
 | 
					          start_time = Time.zone.local(2026, 6, 1) | 
				
			||||||
 | 
					          Export.new(started_at: start_time, collection: "sales", year: 2025).save! | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "does not add any entry for the master manifest (no sales logs)" do | 
				
			||||||
 | 
					          expect(storage_service).not_to receive(:write_file) | 
				
			||||||
 | 
					          expect(export_service.export_xml_sales_logs).to eq({}) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and a previous export has run the same day having sales logs" do | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2025, 5, 1)) | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "and we trigger another full update" do | 
				
			||||||
 | 
					        it "increments the base number" do | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs(full_update: true) | 
				
			||||||
 | 
					          expect(Export.last.base_number).to eq(2) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "resets the increment number" do | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs(full_update: true) | 
				
			||||||
 | 
					          expect(Export.last.increment_number).to eq(1) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "returns a correct archives list for manifest file" do | 
				
			||||||
 | 
					          expect(export_service.export_xml_sales_logs(full_update: true)).to eq({ "core_sales_2025_2026_apr_mar_f0002_inc0001" => start_time }) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "generates a ZIP export file with the expected filename" do | 
				
			||||||
 | 
					          expect(storage_service).to receive(:write_file).with("core_sales_2025_2026_apr_mar_f0002_inc0001.zip", any_args) | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs(full_update: true) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and a previous export has run having no sales logs" do | 
				
			||||||
 | 
					      before { export_service.export_xml_sales_logs } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "doesn't increment the manifest number by 1" do | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(Export.last.increment_number).to eq(1) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and a log has been manually updated since the previous partial export" do | 
				
			||||||
 | 
					      let(:expected_zip_filename) { "core_sales_2025_2026_apr_mar_f0001_inc0002.zip" } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2026, 2, 1), updated_at: Time.zone.local(2026, 2, 27), values_updated_at: Time.zone.local(2026, 2, 29)) | 
				
			||||||
 | 
					        FactoryBot.create(:sales_log, saledate: Time.zone.local(2026, 2, 1), updated_at: Time.zone.local(2026, 2, 27), values_updated_at: Time.zone.local(2026, 2, 29)) | 
				
			||||||
 | 
					        Export.create!(started_at: Time.zone.local(2026, 2, 28), base_number: 1, increment_number: 1, collection: "sales", year: 2025) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML manifest file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					        expected_content = replace_record_number(local_manifest_file.read, 2) | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(export_service.export_xml_sales_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "and one sales log with duplicate reference is available for export" do | 
				
			||||||
 | 
					      let!(:sales_log) { FactoryBot.create(:sales_log, :export, duplicate_set_id: 123) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      def replace_duplicate_set_id(export_file) | 
				
			||||||
 | 
					        export_file.sub!("<duplicate_set_id/>", "<duplicate_set_id>123</duplicate_set_id>") | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it "generates an XML export file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					        expected_content = replace_entity_ids(sales_log, xml_export_file.read) | 
				
			||||||
 | 
					        expected_content = replace_duplicate_set_id(expected_content) | 
				
			||||||
 | 
					        expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					          entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) | 
				
			||||||
 | 
					          expect(entry).not_to be_nil | 
				
			||||||
 | 
					          expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        export_service.export_xml_sales_logs | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context "when exporting only 24/25 collection period" do | 
				
			||||||
 | 
					      let(:start_time) { Time.zone.local(2024, 4, 3) } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      before do | 
				
			||||||
 | 
					        Timecop.freeze(start_time) | 
				
			||||||
 | 
					        Singleton.__init__(FormHandler) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      after do | 
				
			||||||
 | 
					        Timecop.unfreeze | 
				
			||||||
 | 
					        Singleton.__init__(FormHandler) | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context "and one sales log is available for export" do | 
				
			||||||
 | 
					        let!(:sales_log) { FactoryBot.create(:sales_log, :export) } | 
				
			||||||
 | 
					        let(:expected_zip_filename) { "core_sales_2024_2025_apr_mar_f0001_inc0001.zip" } | 
				
			||||||
 | 
					        let(:expected_data_filename) { "core_sales_2024_2025_apr_mar_f0001_inc0001_pt001.xml" } | 
				
			||||||
 | 
					        let(:xml_export_file) { File.open("spec/fixtures/exports/sales_log_2024.xml", "r:UTF-8") } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it "generates an XML export file with the expected content within the ZIP file" do | 
				
			||||||
 | 
					          expected_content = replace_entity_ids(sales_log, xml_export_file.read) | 
				
			||||||
 | 
					          expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| | 
				
			||||||
 | 
					            entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) | 
				
			||||||
 | 
					            expect(entry).not_to be_nil | 
				
			||||||
 | 
					            expect(entry.get_input_stream.read).to eq(expected_content) | 
				
			||||||
 | 
					          end | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          export_service.export_xml_sales_logs(full_update: true, collection_year: 2024) | 
				
			||||||
 | 
					        end | 
				
			||||||
 | 
					      end | 
				
			||||||
 | 
					    end | 
				
			||||||
 | 
					  end | 
				
			||||||
 | 
					end | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue