67 changed files with 3003 additions and 138 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,146 @@ |
|||||||
|
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", |
||||||
|
"DAY", |
||||||
|
"MONTH", |
||||||
|
"YEAR", |
||||||
|
"DUPLICATESET", |
||||||
|
"CREATEDDATE", |
||||||
|
"UPLOADDATE", |
||||||
|
"OWNINGORGID", |
||||||
|
"OWNINGORGNAME", |
||||||
|
"MANINGORGID", |
||||||
|
"MANINGORGNAME", |
||||||
|
"USERNAME", |
||||||
|
"USERNAMEID", |
||||||
|
"PURCHID", |
||||||
|
"TYPE", |
||||||
|
"OWNERSHIP", |
||||||
|
"COLLECTIONYEAR", |
||||||
|
"JOINTMORE", |
||||||
|
"JOINT", |
||||||
|
"BEDS", |
||||||
|
"ETHNIC", |
||||||
|
"ETHNICGROUP1", |
||||||
|
"LIVEINBUYER1", |
||||||
|
"BUILTYPE", |
||||||
|
"PROPTYPE", |
||||||
|
"NOINT", |
||||||
|
"LIVEINBUYER2", |
||||||
|
"PRIVACYNOTICE", |
||||||
|
"WHEEL", |
||||||
|
"HHOLDCOUNT", |
||||||
|
"LA", |
||||||
|
"INCOME1", |
||||||
|
"INC1NK", |
||||||
|
"INC1MORT", |
||||||
|
"INCOME2", |
||||||
|
"INC2NK", |
||||||
|
"SAVINGSNK", |
||||||
|
"SAVINGS", |
||||||
|
"PREVOWN", |
||||||
|
"AMENDEDBY", |
||||||
|
"AMENDEDBYID", |
||||||
|
"MORTGAGE", |
||||||
|
"INC2MORT", |
||||||
|
"HB", |
||||||
|
"FROMBEDS", |
||||||
|
"STAIRCASE", |
||||||
|
"STAIRBOUGHT", |
||||||
|
"STAIROWNED", |
||||||
|
"MRENT", |
||||||
|
"MRENTPRESTAIRCASING", |
||||||
|
"RESALE", |
||||||
|
"DEPOSIT", |
||||||
|
"CASHDIS", |
||||||
|
"DISABLED", |
||||||
|
"VALUE", |
||||||
|
"EQUITY", |
||||||
|
"DISCOUNT", |
||||||
|
"GRANT", |
||||||
|
"PPCODENK", |
||||||
|
"PPOSTC1", |
||||||
|
"PPOSTC2", |
||||||
|
"PREVLOC", |
||||||
|
"PREVLOCNAME", |
||||||
|
"PREVIOUSLAKNOWN", |
||||||
|
"HHREGRES", |
||||||
|
"HHREGRESSTILL", |
||||||
|
"PROPLEN", |
||||||
|
"HASMSCHARGE", |
||||||
|
"MSCHARGE", |
||||||
|
"PREVTEN", |
||||||
|
"MORTGAGEUSED", |
||||||
|
"WCHAIR", |
||||||
|
"ARMEDFORCESSPOUSE", |
||||||
|
"HODAY", |
||||||
|
"HOMONTH", |
||||||
|
"HOYEAR", |
||||||
|
"FROMPROP", |
||||||
|
"SOCPREVTEN", |
||||||
|
"MORTLEN1", |
||||||
|
"EXTRABOR", |
||||||
|
"HHTYPE", |
||||||
|
"POSTCODE", |
||||||
|
"ISLAINFERRED", |
||||||
|
"BULKUPLOADID", |
||||||
|
"VALUE_VALUE_CHECK", |
||||||
|
"PREVSHARED", |
||||||
|
"STAIRCASETOSALE", |
||||||
|
"ETHNICGROUP2", |
||||||
|
"ETHNIC2", |
||||||
|
"BUY2LIVING", |
||||||
|
"PREVTEN2", |
||||||
|
"UPRN", |
||||||
|
"ADDRESS1", |
||||||
|
"ADDRESS2", |
||||||
|
"TOWNCITY", |
||||||
|
"COUNTY", |
||||||
|
"LANAME", |
||||||
|
"CREATIONMETHOD", |
||||||
|
"NATIONALITYALL1", |
||||||
|
"NATIONALITYALL2", |
||||||
|
"MSCHARGE_VALUE_CHECK", |
||||||
|
"ADDRESS1INPUT", |
||||||
|
"POSTCODEINPUT", |
||||||
|
"ADDRESS_SEARCH_VALUE_CHECK", |
||||||
|
"UPRNSELECTED", |
||||||
|
"BULKADDRESS1", |
||||||
|
"BULKADDRESS2", |
||||||
|
"BULKTOWNCITY", |
||||||
|
"BULKCOUNTY", |
||||||
|
"BULKPOSTCODE", |
||||||
|
"BULKLA", |
||||||
|
"CREATEDBY", |
||||||
|
"CREATEDBYID", |
||||||
|
"HASESTATEFEE", |
||||||
|
"ESTATEFEE", |
||||||
|
"FIRSTSTAIR", |
||||||
|
"NUMSTAIR", |
||||||
|
"STAIRLASTDAY", |
||||||
|
"STAIRLASTMONTH", |
||||||
|
"STAIRLASTYEAR", |
||||||
|
"STAIRINITIALYEAR", |
||||||
|
"STAIRINITIALMONTH", |
||||||
|
"STAIRINITIALDAY", |
||||||
|
"HASSERVICECHARGES", |
||||||
|
"SERVICECHARGES",] |
||||||
|
|
||||||
|
(1..6).each do |index| |
||||||
|
EXPORT_FIELDS << "AGE#{index}" |
||||||
|
EXPORT_FIELDS << "ECSTAT#{index}" |
||||||
|
EXPORT_FIELDS << "SEX#{index}" |
||||||
|
end |
||||||
|
|
||||||
|
(2..6).each do |index| |
||||||
|
EXPORT_FIELDS << "RELAT#{index}" |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,156 @@ |
|||||||
|
module Exports |
||||||
|
class SalesLogExportService < Exports::XmlExportService |
||||||
|
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) |
||||||
|
attribute_hash = sales_log.attributes_before_type_cast |
||||||
|
|
||||||
|
attribute_hash["day"] = sales_log.saledate&.day |
||||||
|
attribute_hash["month"] = sales_log.saledate&.month |
||||||
|
attribute_hash["year"] = sales_log.saledate&.year |
||||||
|
|
||||||
|
attribute_hash["createddate"] = sales_log.created_at&.iso8601 |
||||||
|
attribute_hash["createdby"] = sales_log.created_by&.email |
||||||
|
attribute_hash["createdbyid"] = sales_log.created_by_id |
||||||
|
attribute_hash["username"] = sales_log.assigned_to&.email |
||||||
|
attribute_hash["usernameid"] = sales_log.assigned_to_id |
||||||
|
attribute_hash["uploaddate"] = sales_log.updated_at&.iso8601 |
||||||
|
attribute_hash["amendedby"] = sales_log.updated_by&.email |
||||||
|
attribute_hash["amendedbyid"] = sales_log.updated_by_id |
||||||
|
|
||||||
|
attribute_hash["owningorgid"] = sales_log.owning_organisation&.id |
||||||
|
attribute_hash["owningorgname"] = sales_log.owning_organisation&.name |
||||||
|
attribute_hash["maningorgid"] = sales_log.managing_organisation&.id |
||||||
|
attribute_hash["maningorgname"] = sales_log.managing_organisation&.name |
||||||
|
|
||||||
|
attribute_hash["creationmethod"] = sales_log.creation_method_before_type_cast |
||||||
|
attribute_hash["bulkuploadid"] = sales_log.bulk_upload_id |
||||||
|
attribute_hash["collectionyear"] = sales_log.form.start_date.year |
||||||
|
attribute_hash["ownership"] = sales_log.ownershipsch |
||||||
|
attribute_hash["joint"] = sales_log.jointpur |
||||||
|
attribute_hash["ethnicgroup1"] = sales_log.ethnic_group |
||||||
|
attribute_hash["ethnicgroup2"] = sales_log.ethnic_group2 |
||||||
|
attribute_hash["previouslaknown"] = sales_log.previous_la_known |
||||||
|
attribute_hash["hasmscharge"] = sales_log.discounted_ownership_sale? ? sales_log.has_mscharge : nil |
||||||
|
attribute_hash["mscharge"] = sales_log.discounted_ownership_sale? ? sales_log.mscharge : nil |
||||||
|
attribute_hash["hasservicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.has_mscharge : nil |
||||||
|
attribute_hash["servicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.mscharge : nil |
||||||
|
|
||||||
|
attribute_hash["hoday"] = sales_log.hodate&.day |
||||||
|
attribute_hash["homonth"] = sales_log.hodate&.month |
||||||
|
attribute_hash["hoyear"] = sales_log.hodate&.year |
||||||
|
|
||||||
|
attribute_hash["inc1nk"] = sales_log.income1nk |
||||||
|
attribute_hash["inc2nk"] = sales_log.income2nk |
||||||
|
attribute_hash["postcode"] = sales_log.postcode_full |
||||||
|
attribute_hash["islainferred"] = sales_log.is_la_inferred |
||||||
|
attribute_hash["mortlen1"] = sales_log.mortlen |
||||||
|
attribute_hash["ethnic2"] = sales_log.ethnicbuy2 |
||||||
|
attribute_hash["prevten2"] = sales_log.prevtenbuy2 |
||||||
|
|
||||||
|
attribute_hash["address1"] = sales_log.address_line1 |
||||||
|
attribute_hash["address2"] = sales_log.address_line2 |
||||||
|
attribute_hash["towncity"] = sales_log.town_or_city |
||||||
|
attribute_hash["laname"] = LocalAuthority.find_by(code: sales_log.la)&.name |
||||||
|
attribute_hash["address1input"] = sales_log.address_line1_input |
||||||
|
attribute_hash["postcodeinput"] = sales_log.postcode_full_input |
||||||
|
attribute_hash["uprnselected"] = sales_log.uprn_selection |
||||||
|
|
||||||
|
attribute_hash["bulkaddress1"] = sales_log.address_line1_as_entered |
||||||
|
attribute_hash["bulkaddress2"] = sales_log.address_line2_as_entered |
||||||
|
attribute_hash["bulktowncity"] = sales_log.town_or_city_as_entered |
||||||
|
attribute_hash["bulkcounty"] = sales_log.county_as_entered |
||||||
|
attribute_hash["bulkpostcode"] = sales_log.postcode_full_as_entered |
||||||
|
attribute_hash["bulkla"] = sales_log.la_as_entered |
||||||
|
attribute_hash["nationalityall1"] = sales_log.nationality_all |
||||||
|
attribute_hash["nationalityall2"] = sales_log.nationality_all_buyer2 |
||||||
|
attribute_hash["prevlocname"] = LocalAuthority.find_by(code: sales_log.prevloc)&.name |
||||||
|
attribute_hash["liveinbuyer1"] = sales_log.buy1livein |
||||||
|
attribute_hash["liveinbuyer2"] = sales_log.buy2livein |
||||||
|
|
||||||
|
attribute_hash["hasestatefee"] = sales_log.has_management_fee |
||||||
|
attribute_hash["estatefee"] = sales_log.management_fee |
||||||
|
|
||||||
|
attribute_hash["stairlastday"] = sales_log.lasttransaction&.day |
||||||
|
attribute_hash["stairlastmonth"] = sales_log.lasttransaction&.month |
||||||
|
attribute_hash["stairlastyear"] = sales_log.lasttransaction&.year |
||||||
|
|
||||||
|
attribute_hash["stairinitialday"] = sales_log.initialpurchase&.day |
||||||
|
attribute_hash["stairinitialmonth"] = sales_log.initialpurchase&.month |
||||||
|
attribute_hash["stairinitialyear"] = sales_log.initialpurchase&.year |
||||||
|
attribute_hash["mscharge_value_check"] = sales_log.monthly_charges_value_check |
||||||
|
attribute_hash["duplicateset"] = sales_log.duplicate_set_id |
||||||
|
attribute_hash["staircasetosale"] = sales_log.staircasesale |
||||||
|
|
||||||
|
attribute_hash.transform_keys!(&:upcase) |
||||||
|
attribute_hash |
||||||
|
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 |
@ -0,0 +1,5 @@ |
|||||||
|
class AddValuesUpdatedAtToUser < ActiveRecord::Migration[7.2] |
||||||
|
def change |
||||||
|
add_column :users, :values_updated_at, :datetime |
||||||
|
end |
||||||
|
end |
After Width: | Height: | Size: 286 KiB |
After Width: | Height: | Size: 112 KiB |
@ -0,0 +1,207 @@ |
|||||||
|
namespace :bulk_update do |
||||||
|
desc "Update logs with specific criteria and set manual_address_entry_selected to true" |
||||||
|
task update_manual_address_entry_selected: :environment do |
||||||
|
updated_lettings_logs_count = 0 |
||||||
|
lettings_postcode_fixed_count = 0 |
||||||
|
lettings_postcode_fixed_status_changed_count = 0 |
||||||
|
lettings_postcode_not_fixed_status_changed_count = 0 |
||||||
|
lettings_postcode_fixed_status_changed_ids = [] |
||||||
|
lettings_postcode_not_fixed_status_changed_ids = [] |
||||||
|
lettings_updated_without_issue = 0 |
||||||
|
|
||||||
|
updated_sales_logs_count = 0 |
||||||
|
sales_postcode_fixed_count = 0 |
||||||
|
sales_postcode_fixed_status_changed_count = 0 |
||||||
|
sales_postcode_not_fixed_status_changed_count = 0 |
||||||
|
sales_postcode_fixed_status_changed_ids = [] |
||||||
|
sales_postcode_not_fixed_status_changed_ids = [] |
||||||
|
sales_updated_without_issue = 0 |
||||||
|
|
||||||
|
lettings_logs = LettingsLog.filter_by_year(2024) |
||||||
|
.where(status: %w[in_progress completed]) |
||||||
|
.where(needstype: 1, manual_address_entry_selected: false, uprn: nil) |
||||||
|
.where("(address_line1 IS NOT NULL AND address_line1 != '') OR (address_line2 IS NOT NULL AND address_line2 != '') OR (town_or_city IS NOT NULL AND town_or_city != '') OR (county IS NOT NULL AND county != '') OR (postcode_full IS NOT NULL AND postcode_full != '')") |
||||||
|
|
||||||
|
lettings_logs.find_each do |log| |
||||||
|
status_pre_change = log.status |
||||||
|
log.manual_address_entry_selected = true |
||||||
|
if log.save |
||||||
|
updated_lettings_logs_count += 1 |
||||||
|
Rails.logger.info "manual_address_entry_selected updated for lettings log #{log.id}" |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save manual_address_entry_selected changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
|
||||||
|
postcode_fixed = false |
||||||
|
if log.postcode_full.nil? && log.address_line1 == log.address_line1_input |
||||||
|
log.postcode_full = log.postcode_full_input |
||||||
|
if log.save |
||||||
|
lettings_postcode_fixed_count += 1 |
||||||
|
Rails.logger.info "postcode_full updated by address_line1_input for lettings log #{log.id}" |
||||||
|
postcode_fixed = true |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save postcode_full changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if log.postcode_full.nil? && log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered |
||||||
|
log.postcode_full = log.postcode_full_as_entered |
||||||
|
if log.save |
||||||
|
lettings_postcode_fixed_count += 1 |
||||||
|
Rails.logger.info "postcode_full updated by address_line1_as_entered for lettings log #{log.id}" |
||||||
|
postcode_fixed = true |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save postcode_full changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
status_post_change = log.status |
||||||
|
if status_pre_change != status_post_change |
||||||
|
if postcode_fixed |
||||||
|
lettings_postcode_fixed_status_changed_count += 1 |
||||||
|
lettings_postcode_fixed_status_changed_ids << log.id |
||||||
|
else |
||||||
|
lettings_postcode_not_fixed_status_changed_count += 1 |
||||||
|
lettings_postcode_not_fixed_status_changed_ids << log.id |
||||||
|
end |
||||||
|
else |
||||||
|
lettings_updated_without_issue += 1 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
sales_logs = SalesLog.filter_by_year(2024) |
||||||
|
.where(status: %w[in_progress completed]) |
||||||
|
.where(manual_address_entry_selected: false, uprn: nil) |
||||||
|
.where("(address_line1 IS NOT NULL AND address_line1 != '') OR (address_line2 IS NOT NULL AND address_line2 != '') OR (town_or_city IS NOT NULL AND town_or_city != '') OR (county IS NOT NULL AND county != '') OR (postcode_full IS NOT NULL AND postcode_full != '')") |
||||||
|
|
||||||
|
sales_logs.find_each do |log| |
||||||
|
status_pre_change = log.status |
||||||
|
log.manual_address_entry_selected = true |
||||||
|
if log.save |
||||||
|
updated_sales_logs_count += 1 |
||||||
|
Rails.logger.info "manual_address_entry_selected updated for sales log #{log.id}" |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save manual_address_entry_selected changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
|
||||||
|
postcode_fixed = false |
||||||
|
if log.postcode_full.nil? && log.address_line1 == log.address_line1_input |
||||||
|
log.postcode_full = log.postcode_full_input |
||||||
|
if log.save |
||||||
|
sales_postcode_fixed_count += 1 |
||||||
|
Rails.logger.info "postcode_full updated by address_line1_input for sales log #{log.id}" |
||||||
|
postcode_fixed = true |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save postcode_full changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
if log.postcode_full.nil? && log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered |
||||||
|
log.postcode_full = log.postcode_full_as_entered |
||||||
|
if log.save |
||||||
|
sales_postcode_fixed_count += 1 |
||||||
|
Rails.logger.info "postcode_full updated by address_line1_as_entered for sales log #{log.id}" |
||||||
|
postcode_fixed = true |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save postcode_full changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}" |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
status_post_change = log.status |
||||||
|
if status_pre_change != status_post_change |
||||||
|
if postcode_fixed |
||||||
|
sales_postcode_fixed_status_changed_count += 1 |
||||||
|
sales_postcode_fixed_status_changed_ids << log.id |
||||||
|
else |
||||||
|
sales_postcode_not_fixed_status_changed_count += 1 |
||||||
|
sales_postcode_not_fixed_status_changed_ids << log.id |
||||||
|
end |
||||||
|
else |
||||||
|
sales_updated_without_issue += 1 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
puts "#{updated_lettings_logs_count} lettings logs were updated." |
||||||
|
puts "#{lettings_updated_without_issue} lettings logs were updated without issue." |
||||||
|
puts "#{lettings_postcode_fixed_count} lettings logs where postcode fix was applied." |
||||||
|
puts "#{lettings_postcode_fixed_status_changed_count} lettings logs with postcode fix and status changed." |
||||||
|
puts "#{lettings_postcode_not_fixed_status_changed_count} lettings logs without postcode fix and status changed." |
||||||
|
puts "IDs of lettings logs with postcode fix and status changed: [#{lettings_postcode_fixed_status_changed_ids.join(', ')}]" |
||||||
|
puts "IDs of lettings logs without postcode fix and status changed: [#{lettings_postcode_not_fixed_status_changed_ids.join(', ')}]" |
||||||
|
|
||||||
|
lettings_postcode_fixed_org_counts = LettingsLog.where(id: lettings_postcode_fixed_status_changed_ids).group(:owning_organisation_id).count |
||||||
|
lettings_postcode_fixed_org_counts.each do |org_id, count| |
||||||
|
puts "Org #{org_id}: #{count} logs with postcode fix and status changed." |
||||||
|
end |
||||||
|
|
||||||
|
lettings_postcode_not_fixed_org_counts = LettingsLog.where(id: lettings_postcode_not_fixed_status_changed_ids).group(:owning_organisation_id).count |
||||||
|
lettings_postcode_not_fixed_org_counts.each do |org_id, count| |
||||||
|
puts "Org #{org_id}: #{count} logs without postcode fix and status changed." |
||||||
|
end |
||||||
|
|
||||||
|
puts "#{updated_sales_logs_count} sales logs were updated." |
||||||
|
puts "#{sales_updated_without_issue} sales logs were updated without issue." |
||||||
|
puts "#{sales_postcode_fixed_count} sales logs where postcode fix was applied." |
||||||
|
puts "#{sales_postcode_fixed_status_changed_count} sales logs with postcode fix and status changed." |
||||||
|
puts "#{sales_postcode_not_fixed_status_changed_count} sales logs without postcode fix and status changed." |
||||||
|
puts "IDs of sales logs with postcode fix and status changed: [#{sales_postcode_fixed_status_changed_ids.join(', ')}]" |
||||||
|
puts "IDs of sales logs without postcode fix and status changed: [#{sales_postcode_not_fixed_status_changed_ids.join(', ')}]" |
||||||
|
|
||||||
|
sales_postcode_fixed_org_counts = SalesLog.where(id: sales_postcode_fixed_status_changed_ids).group(:owning_organisation_id).count |
||||||
|
sales_postcode_fixed_org_counts.each do |org_id, count| |
||||||
|
puts "Org #{org_id}: #{count} logs with postcode fix and status changed." |
||||||
|
end |
||||||
|
|
||||||
|
sales_postcode_not_fixed_org_counts = SalesLog.where(id: sales_postcode_not_fixed_status_changed_ids).group(:owning_organisation_id).count |
||||||
|
sales_postcode_not_fixed_org_counts.each do |org_id, count| |
||||||
|
puts "Org #{org_id}: #{count} logs without postcode fix and status changed." |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
desc "Find logs to fix and update postcode_full if conditions are met" |
||||||
|
task update_postcode_full_preexisting_manual_entry_logs: :environment do |
||||||
|
updated_count = 0 |
||||||
|
fixed_count = 0 |
||||||
|
not_updated_count = 0 |
||||||
|
not_updated_ids = [] |
||||||
|
updated_but_not_fixed_ids = [] |
||||||
|
|
||||||
|
logs_to_fix = LettingsLog.filter_by_year(2024).where(manual_address_entry_selected: true, uprn: nil, status: "in_progress", postcode_full: nil, updated_at: Time.zone.parse("2025-03-19 16:00:00")..Time.zone.parse("2025-03-19 17:00:00")) |
||||||
|
|
||||||
|
logs_to_fix.find_each do |log| |
||||||
|
previous_version = log.versions[-2] |
||||||
|
previous_status = previous_version&.reify&.status |
||||||
|
|
||||||
|
if log.address_line1 == log.address_line1_input |
||||||
|
log.postcode_full = log.postcode_full_input |
||||||
|
elsif log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered |
||||||
|
log.postcode_full = log.postcode_full_as_entered |
||||||
|
end |
||||||
|
|
||||||
|
if log.postcode_full.present? |
||||||
|
if log.save |
||||||
|
Rails.logger.info "Updated postcode_full for lettings log #{log.id}" |
||||||
|
updated_count += 1 |
||||||
|
if log.status == previous_status |
||||||
|
fixed_count += 1 |
||||||
|
else |
||||||
|
updated_but_not_fixed_ids << log.id |
||||||
|
end |
||||||
|
else |
||||||
|
Rails.logger.info "Could not save changes to lettings log #{log.id}: #{log.errors.full_messages.join(', ')}" |
||||||
|
not_updated_count += 1 |
||||||
|
not_updated_ids << log.id |
||||||
|
end |
||||||
|
else |
||||||
|
not_updated_count += 1 |
||||||
|
not_updated_ids << log.id |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
puts "#{updated_count} logs updated." |
||||||
|
puts "#{fixed_count} logs fixed." |
||||||
|
puts "#{not_updated_count} logs not updated." |
||||||
|
puts "IDs of logs not updated: [#{not_updated_ids.join(', ')}]" |
||||||
|
puts "IDs of logs updated but not fixed: [#{updated_but_not_fixed_ids.join(', ')}]" |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,176 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<forms> |
||||||
|
<form> |
||||||
|
<status>2</status> |
||||||
|
<tenancycode>BZ737</tenancycode> |
||||||
|
<age1>35</age1> |
||||||
|
<sex1>F</sex1> |
||||||
|
<ethnic>2</ethnic> |
||||||
|
<prevten>6</prevten> |
||||||
|
<ecstat1>0</ecstat1> |
||||||
|
<hhmemb>2</hhmemb> |
||||||
|
<age2>32</age2> |
||||||
|
<sex2>M</sex2> |
||||||
|
<ecstat2>6</ecstat2> |
||||||
|
<age3/> |
||||||
|
<sex3/> |
||||||
|
<ecstat3/> |
||||||
|
<age4/> |
||||||
|
<sex4/> |
||||||
|
<ecstat4/> |
||||||
|
<age5/> |
||||||
|
<sex5/> |
||||||
|
<ecstat5/> |
||||||
|
<age6/> |
||||||
|
<sex6/> |
||||||
|
<ecstat6/> |
||||||
|
<age7/> |
||||||
|
<sex7/> |
||||||
|
<ecstat7/> |
||||||
|
<age8/> |
||||||
|
<sex8/> |
||||||
|
<ecstat8/> |
||||||
|
<homeless>1</homeless> |
||||||
|
<underoccupation_benefitcap>4</underoccupation_benefitcap> |
||||||
|
<leftreg>4</leftreg> |
||||||
|
<reservist>1</reservist> |
||||||
|
<illness>1</illness> |
||||||
|
<preg_occ>2</preg_occ> |
||||||
|
<startertenancy>1</startertenancy> |
||||||
|
<tenancylength>5</tenancylength> |
||||||
|
<tenancy>4</tenancy> |
||||||
|
<ppostcode_full>A1 1AA</ppostcode_full> |
||||||
|
<rsnvac>6</rsnvac> |
||||||
|
<unittype_gn>7</unittype_gn> |
||||||
|
<beds>3</beds> |
||||||
|
<wchair>1</wchair> |
||||||
|
<earnings>268</earnings> |
||||||
|
<incfreq>1</incfreq> |
||||||
|
<benefits>1</benefits> |
||||||
|
<period>2</period> |
||||||
|
<layear>2</layear> |
||||||
|
<waityear>7</waityear> |
||||||
|
<postcode_full>AA1 1AA</postcode_full> |
||||||
|
<reasonpref>1</reasonpref> |
||||||
|
<cbl>0</cbl> |
||||||
|
<chr>1</chr> |
||||||
|
<cap>0</cap> |
||||||
|
<reasonother/> |
||||||
|
<housingneeds_a>1</housingneeds_a> |
||||||
|
<housingneeds_b>0</housingneeds_b> |
||||||
|
<housingneeds_c>0</housingneeds_c> |
||||||
|
<housingneeds_f>0</housingneeds_f> |
||||||
|
<housingneeds_g>0</housingneeds_g> |
||||||
|
<housingneeds_h>0</housingneeds_h> |
||||||
|
<illness_type_1>0</illness_type_1> |
||||||
|
<illness_type_2>1</illness_type_2> |
||||||
|
<illness_type_3>0</illness_type_3> |
||||||
|
<illness_type_4>0</illness_type_4> |
||||||
|
<illness_type_8>0</illness_type_8> |
||||||
|
<illness_type_5>0</illness_type_5> |
||||||
|
<illness_type_6>0</illness_type_6> |
||||||
|
<illness_type_7>0</illness_type_7> |
||||||
|
<illness_type_9>0</illness_type_9> |
||||||
|
<illness_type_10>0</illness_type_10> |
||||||
|
<rp_homeless>0</rp_homeless> |
||||||
|
<rp_insan_unsat>1</rp_insan_unsat> |
||||||
|
<rp_medwel>0</rp_medwel> |
||||||
|
<rp_hardship>0</rp_hardship> |
||||||
|
<rp_dontknow>0</rp_dontknow> |
||||||
|
<tenancyother/> |
||||||
|
<net_income_value_check/> |
||||||
|
<irproduct_other/> |
||||||
|
<reason>4</reason> |
||||||
|
<propcode>123</propcode> |
||||||
|
<la>E09000033</la> |
||||||
|
<prevloc>E07000105</prevloc> |
||||||
|
<hb>6</hb> |
||||||
|
<hbrentshortfall>1</hbrentshortfall> |
||||||
|
<mrcdate>2022-05-05T10:36:49+01:00</mrcdate> |
||||||
|
<incref>0</incref> |
||||||
|
<startdate>2025-04-03T00:00:00+01:00</startdate> |
||||||
|
<armedforces>1</armedforces> |
||||||
|
<unitletas>2</unitletas> |
||||||
|
<builtype>1</builtype> |
||||||
|
<voiddate>2021-11-03T00:00:00+00:00</voiddate> |
||||||
|
<renttype>2</renttype> |
||||||
|
<needstype>1</needstype> |
||||||
|
<lettype>7</lettype> |
||||||
|
<totchild>0</totchild> |
||||||
|
<totelder>0</totelder> |
||||||
|
<totadult>2</totadult> |
||||||
|
<nocharge>0</nocharge> |
||||||
|
<referral>2</referral> |
||||||
|
<brent>200.0</brent> |
||||||
|
<scharge>50.0</scharge> |
||||||
|
<pscharge>40.0</pscharge> |
||||||
|
<supcharg>35.0</supcharg> |
||||||
|
<tcharge>325.0</tcharge> |
||||||
|
<tshortfall>12.0</tshortfall> |
||||||
|
<ppcodenk>0</ppcodenk> |
||||||
|
<has_benefits>1</has_benefits> |
||||||
|
<renewal>0</renewal> |
||||||
|
<wrent>100.0</wrent> |
||||||
|
<wscharge>25.0</wscharge> |
||||||
|
<wpschrge>20.0</wpschrge> |
||||||
|
<wsupchrg>17.5</wsupchrg> |
||||||
|
<wtcharge>162.5</wtcharge> |
||||||
|
<wtshortfall>6.0</wtshortfall> |
||||||
|
<refused>0</refused> |
||||||
|
<housingneeds>1</housingneeds> |
||||||
|
<wchchrg/> |
||||||
|
<newprop>2</newprop> |
||||||
|
<relat2>P</relat2> |
||||||
|
<relat3/> |
||||||
|
<relat4/> |
||||||
|
<relat5/> |
||||||
|
<relat6/> |
||||||
|
<relat7/> |
||||||
|
<relat8/> |
||||||
|
<rent_value_check/> |
||||||
|
<lar>2</lar> |
||||||
|
<irproduct/> |
||||||
|
<joint>3</joint> |
||||||
|
<sheltered/> |
||||||
|
<hhtype>4</hhtype> |
||||||
|
<new_old>2</new_old> |
||||||
|
<vacdays>1064</vacdays> |
||||||
|
<bulk_upload_id>1</bulk_upload_id> |
||||||
|
<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/> |
||||||
|
<discarded_at/> |
||||||
|
<creation_method>2</creation_method> |
||||||
|
<supcharg_value_check/> |
||||||
|
<scharge_value_check/> |
||||||
|
<pscharge_value_check/> |
||||||
|
<duplicate_set_id/> |
||||||
|
<accessible_register>0</accessible_register> |
||||||
|
<nationality_all>826</nationality_all> |
||||||
|
<address_line1_as_entered>address line 1 as entered</address_line1_as_entered> |
||||||
|
<address_line2_as_entered>address line 2 as entered</address_line2_as_entered> |
||||||
|
<town_or_city_as_entered>town or city as entered</town_or_city_as_entered> |
||||||
|
<county_as_entered>county as entered</county_as_entered> |
||||||
|
<postcode_full_as_entered>AB1 2CD</postcode_full_as_entered> |
||||||
|
<la_as_entered>la as entered</la_as_entered> |
||||||
|
<formid>{id}</formid> |
||||||
|
<owningorgid>{owning_org_id}</owningorgid> |
||||||
|
<owningorgname>{owning_org_name}</owningorgname> |
||||||
|
<hcnum>1234</hcnum> |
||||||
|
<maningorgid>{managing_org_id}</maningorgid> |
||||||
|
<maningorgname>{managing_org_name}</maningorgname> |
||||||
|
<manhcnum>1234</manhcnum> |
||||||
|
<createddate>2025-04-03T00:00:00+01:00</createddate> |
||||||
|
<uploaddate>2025-04-03T00:00:00+01:00</uploaddate> |
||||||
|
<log_id>{log_id}</log_id> |
||||||
|
<assigned_to>test1@example.com</assigned_to> |
||||||
|
<created_by>test1@example.com</created_by> |
||||||
|
<amended_by/> |
||||||
|
<renttype_detail>2</renttype_detail> |
||||||
|
<providertype>1</providertype> |
||||||
|
</form> |
||||||
|
</forms> |
@ -0,0 +1,154 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<forms> |
||||||
|
<form> |
||||||
|
<ID>{id}</ID> |
||||||
|
<STATUS>1</STATUS> |
||||||
|
<PURCHID>123</PURCHID> |
||||||
|
<TYPE>8</TYPE> |
||||||
|
<JOINTMORE>1</JOINTMORE> |
||||||
|
<BEDS>2</BEDS> |
||||||
|
<AGE1>27</AGE1> |
||||||
|
<SEX1>F</SEX1> |
||||||
|
<ETHNIC>17</ETHNIC> |
||||||
|
<BUILTYPE>1</BUILTYPE> |
||||||
|
<PROPTYPE>1</PROPTYPE> |
||||||
|
<AGE2>33</AGE2> |
||||||
|
<RELAT2>P</RELAT2> |
||||||
|
<SEX2>X</SEX2> |
||||||
|
<NOINT>2</NOINT> |
||||||
|
<ECSTAT2>1</ECSTAT2> |
||||||
|
<PRIVACYNOTICE>1</PRIVACYNOTICE> |
||||||
|
<ECSTAT1>1</ECSTAT1> |
||||||
|
<WHEEL>1</WHEEL> |
||||||
|
<HHOLDCOUNT>4</HHOLDCOUNT> |
||||||
|
<AGE3>14</AGE3> |
||||||
|
<LA>E09000033</LA> |
||||||
|
<INCOME1>10000</INCOME1> |
||||||
|
<AGE4>18</AGE4> |
||||||
|
<AGE5>40</AGE5> |
||||||
|
<AGE6>40</AGE6> |
||||||
|
<INC1MORT>1</INC1MORT> |
||||||
|
<INCOME2>10000</INCOME2> |
||||||
|
<SAVINGSNK>1</SAVINGSNK> |
||||||
|
<SAVINGS/> |
||||||
|
<PREVOWN>1</PREVOWN> |
||||||
|
<SEX3>F</SEX3> |
||||||
|
<MORTGAGE>20000.0</MORTGAGE> |
||||||
|
<INC2MORT>1</INC2MORT> |
||||||
|
<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> |
||||||
|
<FROMBEDS/> |
||||||
|
<STAIRCASE/> |
||||||
|
<STAIRBOUGHT/> |
||||||
|
<STAIROWNED/> |
||||||
|
<MRENT/> |
||||||
|
<RESALE/> |
||||||
|
<DEPOSIT>80000.0</DEPOSIT> |
||||||
|
<CASHDIS/> |
||||||
|
<DISABLED>1</DISABLED> |
||||||
|
<VALUE>110000.0</VALUE> |
||||||
|
<EQUITY/> |
||||||
|
<DISCOUNT/> |
||||||
|
<GRANT>10000.0</GRANT> |
||||||
|
<PPCODENK>0</PPCODENK> |
||||||
|
<PPOSTC1>SW1A</PPOSTC1> |
||||||
|
<PPOSTC2>1AA</PPOSTC2> |
||||||
|
<PREVLOC>E09000033</PREVLOC> |
||||||
|
<HHREGRES>7</HHREGRES> |
||||||
|
<HHREGRESSTILL/> |
||||||
|
<PROPLEN/> |
||||||
|
<MSCHARGE>100.0</MSCHARGE> |
||||||
|
<PREVTEN>1</PREVTEN> |
||||||
|
<MORTGAGEUSED>1</MORTGAGEUSED> |
||||||
|
<WCHAIR>1</WCHAIR> |
||||||
|
<ARMEDFORCESSPOUSE>5</ARMEDFORCESSPOUSE> |
||||||
|
<HODAY/> |
||||||
|
<HOMONTH/> |
||||||
|
<HOYEAR/> |
||||||
|
<FROMPROP/> |
||||||
|
<SOCPREVTEN/> |
||||||
|
<EXTRABOR>1</EXTRABOR> |
||||||
|
<HHTYPE>6</HHTYPE> |
||||||
|
<VALUE_VALUE_CHECK/> |
||||||
|
<PREVSHARED>2</PREVSHARED> |
||||||
|
<BUY2LIVING>3</BUY2LIVING> |
||||||
|
<UPRN/> |
||||||
|
<COUNTY/> |
||||||
|
<ADDRESS_SEARCH_VALUE_CHECK/> |
||||||
|
<FIRSTSTAIR/> |
||||||
|
<NUMSTAIR/> |
||||||
|
<MRENTPRESTAIRCASING/> |
||||||
|
<DAY>1</DAY> |
||||||
|
<MONTH>3</MONTH> |
||||||
|
<YEAR>2026</YEAR> |
||||||
|
<CREATEDDATE>2026-03-01T00:00:00+00:00</CREATEDDATE> |
||||||
|
<CREATEDBY>{created_by_email}</CREATEDBY> |
||||||
|
<CREATEDBYID>{created_by_id}</CREATEDBYID> |
||||||
|
<USERNAME>{assigned_to_email}</USERNAME> |
||||||
|
<USERNAMEID>{assigned_to_id}</USERNAMEID> |
||||||
|
<UPLOADDATE>2026-03-01T00:00:00+00:00</UPLOADDATE> |
||||||
|
<AMENDEDBY/> |
||||||
|
<AMENDEDBYID/> |
||||||
|
<OWNINGORGID>{owning_org_id}</OWNINGORGID> |
||||||
|
<OWNINGORGNAME>{owning_org_name}</OWNINGORGNAME> |
||||||
|
<MANINGORGID>{managing_org_id}</MANINGORGID> |
||||||
|
<MANINGORGNAME>{managing_org_name}</MANINGORGNAME> |
||||||
|
<CREATIONMETHOD>1</CREATIONMETHOD> |
||||||
|
<BULKUPLOADID/> |
||||||
|
<COLLECTIONYEAR>2025</COLLECTIONYEAR> |
||||||
|
<OWNERSHIP>2</OWNERSHIP> |
||||||
|
<JOINT>1</JOINT> |
||||||
|
<ETHNICGROUP1>17</ETHNICGROUP1> |
||||||
|
<ETHNICGROUP2>17</ETHNICGROUP2> |
||||||
|
<PREVIOUSLAKNOWN>1</PREVIOUSLAKNOWN> |
||||||
|
<HASMSCHARGE>1</HASMSCHARGE> |
||||||
|
<HASSERVICECHARGES/> |
||||||
|
<SERVICECHARGES/> |
||||||
|
<INC1NK>0</INC1NK> |
||||||
|
<INC2NK>0</INC2NK> |
||||||
|
<POSTCODE>SW1A 1AA</POSTCODE> |
||||||
|
<ISLAINFERRED>true</ISLAINFERRED> |
||||||
|
<MORTLEN1>10</MORTLEN1> |
||||||
|
<ETHNIC2/> |
||||||
|
<PREVTEN2/> |
||||||
|
<ADDRESS1>Address line 1</ADDRESS1> |
||||||
|
<ADDRESS2/> |
||||||
|
<TOWNCITY>City</TOWNCITY> |
||||||
|
<LANAME>Westminster</LANAME> |
||||||
|
<ADDRESS1INPUT>Address line 1</ADDRESS1INPUT> |
||||||
|
<POSTCODEINPUT>SW1A 1AA</POSTCODEINPUT> |
||||||
|
<UPRNSELECTED/> |
||||||
|
<BULKADDRESS1/> |
||||||
|
<BULKADDRESS2/> |
||||||
|
<BULKTOWNCITY/> |
||||||
|
<BULKCOUNTY/> |
||||||
|
<BULKPOSTCODE/> |
||||||
|
<BULKLA/> |
||||||
|
<NATIONALITYALL1>826</NATIONALITYALL1> |
||||||
|
<NATIONALITYALL2>826</NATIONALITYALL2> |
||||||
|
<PREVLOCNAME>Westminster</PREVLOCNAME> |
||||||
|
<LIVEINBUYER1>1</LIVEINBUYER1> |
||||||
|
<LIVEINBUYER2>1</LIVEINBUYER2> |
||||||
|
<HASESTATEFEE/> |
||||||
|
<ESTATEFEE/> |
||||||
|
<STAIRLASTDAY/> |
||||||
|
<STAIRLASTMONTH/> |
||||||
|
<STAIRLASTYEAR/> |
||||||
|
<STAIRINITIALDAY/> |
||||||
|
<STAIRINITIALMONTH/> |
||||||
|
<STAIRINITIALYEAR/> |
||||||
|
<MSCHARGE_VALUE_CHECK/> |
||||||
|
<DUPLICATESET/> |
||||||
|
<STAIRCASETOSALE/> |
||||||
|
</form> |
||||||
|
</forms> |
@ -0,0 +1,154 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<forms> |
||||||
|
<form> |
||||||
|
<ID>{id}</ID> |
||||||
|
<STATUS>2</STATUS> |
||||||
|
<PURCHID>123</PURCHID> |
||||||
|
<TYPE>8</TYPE> |
||||||
|
<JOINTMORE>1</JOINTMORE> |
||||||
|
<BEDS>2</BEDS> |
||||||
|
<AGE1>27</AGE1> |
||||||
|
<SEX1>F</SEX1> |
||||||
|
<ETHNIC>17</ETHNIC> |
||||||
|
<BUILTYPE>1</BUILTYPE> |
||||||
|
<PROPTYPE>1</PROPTYPE> |
||||||
|
<AGE2>33</AGE2> |
||||||
|
<RELAT2>P</RELAT2> |
||||||
|
<SEX2>X</SEX2> |
||||||
|
<NOINT>2</NOINT> |
||||||
|
<ECSTAT2>1</ECSTAT2> |
||||||
|
<PRIVACYNOTICE>1</PRIVACYNOTICE> |
||||||
|
<ECSTAT1>1</ECSTAT1> |
||||||
|
<WHEEL>1</WHEEL> |
||||||
|
<HHOLDCOUNT>4</HHOLDCOUNT> |
||||||
|
<AGE3>14</AGE3> |
||||||
|
<LA>E09000033</LA> |
||||||
|
<INCOME1>10000</INCOME1> |
||||||
|
<AGE4>18</AGE4> |
||||||
|
<AGE5>40</AGE5> |
||||||
|
<AGE6>40</AGE6> |
||||||
|
<INC1MORT>1</INC1MORT> |
||||||
|
<INCOME2>10000</INCOME2> |
||||||
|
<SAVINGSNK>1</SAVINGSNK> |
||||||
|
<SAVINGS/> |
||||||
|
<PREVOWN>1</PREVOWN> |
||||||
|
<SEX3>F</SEX3> |
||||||
|
<MORTGAGE>20000.0</MORTGAGE> |
||||||
|
<INC2MORT>1</INC2MORT> |
||||||
|
<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> |
||||||
|
<FROMBEDS/> |
||||||
|
<STAIRCASE/> |
||||||
|
<STAIRBOUGHT/> |
||||||
|
<STAIROWNED/> |
||||||
|
<MRENT/> |
||||||
|
<RESALE/> |
||||||
|
<DEPOSIT>80000.0</DEPOSIT> |
||||||
|
<CASHDIS/> |
||||||
|
<DISABLED>1</DISABLED> |
||||||
|
<VALUE>110000.0</VALUE> |
||||||
|
<EQUITY/> |
||||||
|
<DISCOUNT/> |
||||||
|
<GRANT>10000.0</GRANT> |
||||||
|
<PPCODENK>0</PPCODENK> |
||||||
|
<PPOSTC1>SW1A</PPOSTC1> |
||||||
|
<PPOSTC2>1AA</PPOSTC2> |
||||||
|
<PREVLOC>E09000033</PREVLOC> |
||||||
|
<HHREGRES>7</HHREGRES> |
||||||
|
<HHREGRESSTILL/> |
||||||
|
<PROPLEN/> |
||||||
|
<MSCHARGE>100.0</MSCHARGE> |
||||||
|
<PREVTEN>1</PREVTEN> |
||||||
|
<MORTGAGEUSED>1</MORTGAGEUSED> |
||||||
|
<WCHAIR>1</WCHAIR> |
||||||
|
<ARMEDFORCESSPOUSE>5</ARMEDFORCESSPOUSE> |
||||||
|
<HODAY/> |
||||||
|
<HOMONTH/> |
||||||
|
<HOYEAR/> |
||||||
|
<FROMPROP/> |
||||||
|
<SOCPREVTEN/> |
||||||
|
<EXTRABOR>1</EXTRABOR> |
||||||
|
<HHTYPE>6</HHTYPE> |
||||||
|
<VALUE_VALUE_CHECK/> |
||||||
|
<PREVSHARED>2</PREVSHARED> |
||||||
|
<BUY2LIVING>3</BUY2LIVING> |
||||||
|
<UPRN/> |
||||||
|
<COUNTY/> |
||||||
|
<ADDRESS_SEARCH_VALUE_CHECK/> |
||||||
|
<FIRSTSTAIR/> |
||||||
|
<NUMSTAIR/> |
||||||
|
<MRENTPRESTAIRCASING/> |
||||||
|
<DAY>3</DAY> |
||||||
|
<MONTH>4</MONTH> |
||||||
|
<YEAR>2024</YEAR> |
||||||
|
<CREATEDDATE>2024-04-03T00:00:00+01:00</CREATEDDATE> |
||||||
|
<CREATEDBY>{created_by_email}</CREATEDBY> |
||||||
|
<CREATEDBYID>{created_by_id}</CREATEDBYID> |
||||||
|
<USERNAME>{assigned_to_email}</USERNAME> |
||||||
|
<USERNAMEID>{assigned_to_id}</USERNAMEID> |
||||||
|
<UPLOADDATE>2024-04-03T00:00:00+01:00</UPLOADDATE> |
||||||
|
<AMENDEDBY/> |
||||||
|
<AMENDEDBYID/> |
||||||
|
<OWNINGORGID>{owning_org_id}</OWNINGORGID> |
||||||
|
<OWNINGORGNAME>{owning_org_name}</OWNINGORGNAME> |
||||||
|
<MANINGORGID>{managing_org_id}</MANINGORGID> |
||||||
|
<MANINGORGNAME>{managing_org_name}</MANINGORGNAME> |
||||||
|
<CREATIONMETHOD>1</CREATIONMETHOD> |
||||||
|
<BULKUPLOADID/> |
||||||
|
<COLLECTIONYEAR>2024</COLLECTIONYEAR> |
||||||
|
<OWNERSHIP>2</OWNERSHIP> |
||||||
|
<JOINT>1</JOINT> |
||||||
|
<ETHNICGROUP1>17</ETHNICGROUP1> |
||||||
|
<ETHNICGROUP2>17</ETHNICGROUP2> |
||||||
|
<PREVIOUSLAKNOWN>1</PREVIOUSLAKNOWN> |
||||||
|
<HASMSCHARGE>1</HASMSCHARGE> |
||||||
|
<HASSERVICECHARGES/> |
||||||
|
<SERVICECHARGES/> |
||||||
|
<INC1NK>0</INC1NK> |
||||||
|
<INC2NK>0</INC2NK> |
||||||
|
<POSTCODE>SW1A 1AA</POSTCODE> |
||||||
|
<ISLAINFERRED>true</ISLAINFERRED> |
||||||
|
<MORTLEN1>10</MORTLEN1> |
||||||
|
<ETHNIC2/> |
||||||
|
<PREVTEN2/> |
||||||
|
<ADDRESS1>Address line 1</ADDRESS1> |
||||||
|
<ADDRESS2/> |
||||||
|
<TOWNCITY>City</TOWNCITY> |
||||||
|
<LANAME>Westminster</LANAME> |
||||||
|
<ADDRESS1INPUT>Address line 1</ADDRESS1INPUT> |
||||||
|
<POSTCODEINPUT>SW1A 1AA</POSTCODEINPUT> |
||||||
|
<UPRNSELECTED/> |
||||||
|
<BULKADDRESS1/> |
||||||
|
<BULKADDRESS2/> |
||||||
|
<BULKTOWNCITY/> |
||||||
|
<BULKCOUNTY/> |
||||||
|
<BULKPOSTCODE/> |
||||||
|
<BULKLA/> |
||||||
|
<NATIONALITYALL1>826</NATIONALITYALL1> |
||||||
|
<NATIONALITYALL2>826</NATIONALITYALL2> |
||||||
|
<PREVLOCNAME>Westminster</PREVLOCNAME> |
||||||
|
<LIVEINBUYER1>1</LIVEINBUYER1> |
||||||
|
<LIVEINBUYER2>1</LIVEINBUYER2> |
||||||
|
<HASESTATEFEE/> |
||||||
|
<ESTATEFEE/> |
||||||
|
<STAIRLASTDAY/> |
||||||
|
<STAIRLASTMONTH/> |
||||||
|
<STAIRLASTYEAR/> |
||||||
|
<STAIRINITIALDAY/> |
||||||
|
<STAIRINITIALMONTH/> |
||||||
|
<STAIRINITIALYEAR/> |
||||||
|
<MSCHARGE_VALUE_CHECK/> |
||||||
|
<DUPLICATESET/> |
||||||
|
<STAIRCASETOSALE/> |
||||||
|
</form> |
||||||
|
</forms> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,211 @@ |
|||||||
|
require "rails_helper" |
||||||
|
require "rake" |
||||||
|
|
||||||
|
RSpec.describe "update_manual_address_entry_selected_preexisting_logs_spec", type: :task do |
||||||
|
before do |
||||||
|
Rake.application.rake_require("tasks/update_manual_address_entry_selected_prexisting_logs") |
||||||
|
Rake::Task.define_task(:environment) |
||||||
|
task.reenable |
||||||
|
end |
||||||
|
|
||||||
|
describe "bulk_update:update_manual_address_entry_selected" do |
||||||
|
let(:task) { Rake::Task["bulk_update:update_manual_address_entry_selected"] } |
||||||
|
|
||||||
|
let(:lettings_log_uprn_entered) do |
||||||
|
build(:lettings_log, :completed, startdate: Time.zone.local(2024, 6, 1), needstype: 1, manual_address_entry_selected: false) |
||||||
|
end |
||||||
|
|
||||||
|
let(:lettings_log_uprn_found) do |
||||||
|
build(:lettings_log, :completed, startdate: Time.zone.local(2024, 9, 1), needstype: 1, manual_address_entry_selected: false, address_line1_input: "1 Test Street", postcode_full_input: "SW1 1AA") |
||||||
|
end |
||||||
|
|
||||||
|
let(:lettings_log_address_fields_not_entered) do |
||||||
|
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 9, 1), needstype: 1) |
||||||
|
end |
||||||
|
|
||||||
|
let(:lettings_log_address_manually_entered) do |
||||||
|
build(:lettings_log, :completed_without_uprn, startdate: Time.zone.local(2024, 12, 1), needstype: 1) |
||||||
|
end |
||||||
|
|
||||||
|
let(:sales_log_uprn_entered) do |
||||||
|
build(:sales_log, :completed, saledate: Time.zone.local(2024, 12, 1), manual_address_entry_selected: false) |
||||||
|
end |
||||||
|
|
||||||
|
let(:sales_log_uprn_found) do |
||||||
|
build(:sales_log, :completed, saledate: Time.zone.local(2024, 7, 1), manual_address_entry_selected: false, address_line1_input: "1 Test Street", postcode_full_input: "SW1 1AA") |
||||||
|
end |
||||||
|
|
||||||
|
let(:sales_log_address_fields_not_entered) do |
||||||
|
build(:sales_log, :inprogress_without_address_fields, saledate: Time.zone.local(2024, 12, 30)) |
||||||
|
end |
||||||
|
|
||||||
|
let(:sales_log_address_manually_entered) do |
||||||
|
build(:sales_log, :completed_without_uprn, saledate: Time.zone.local(2024, 12, 30)) |
||||||
|
end |
||||||
|
|
||||||
|
context "when running the task" do |
||||||
|
context "when logs do not meet the criteria" do |
||||||
|
before do |
||||||
|
lettings_log_uprn_found.save!(validate: false) |
||||||
|
lettings_log_uprn_entered.save!(validate: false) |
||||||
|
lettings_log_address_fields_not_entered.save!(validate: false) |
||||||
|
|
||||||
|
sales_log_uprn_found.save!(validate: false) |
||||||
|
sales_log_uprn_entered.save!(validate: false) |
||||||
|
sales_log_address_fields_not_entered.save!(validate: false) |
||||||
|
end |
||||||
|
|
||||||
|
it "does not update logs with a UPRN entered" do |
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_uprn_entered.reload |
||||||
|
sales_log_uprn_entered.reload |
||||||
|
|
||||||
|
expect(lettings_log_uprn_entered.manual_address_entry_selected).to be false |
||||||
|
expect(lettings_log_uprn_entered.uprn).to eq("10033558653") |
||||||
|
expect(sales_log_uprn_entered.manual_address_entry_selected).to be false |
||||||
|
expect(sales_log_uprn_entered.uprn).to eq("10033558653") |
||||||
|
end |
||||||
|
|
||||||
|
it "does not update logs with a UPRN found" do |
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_uprn_found.reload |
||||||
|
sales_log_uprn_found.reload |
||||||
|
|
||||||
|
expect(lettings_log_uprn_found.manual_address_entry_selected).to be false |
||||||
|
expect(lettings_log_uprn_found.uprn).to eq("10033558653") |
||||||
|
expect(sales_log_uprn_found.manual_address_entry_selected).to be false |
||||||
|
expect(sales_log_uprn_found.uprn).to eq("10033558653") |
||||||
|
end |
||||||
|
|
||||||
|
it "does not update logs with no UPRN or address fields entered" do |
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_address_fields_not_entered.reload |
||||||
|
sales_log_address_fields_not_entered.reload |
||||||
|
|
||||||
|
expect(lettings_log_address_fields_not_entered.manual_address_entry_selected).to be false |
||||||
|
expect(sales_log_address_fields_not_entered.manual_address_entry_selected).to be false |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when logs do meet the criteria" do |
||||||
|
before do |
||||||
|
lettings_log_address_manually_entered.manual_address_entry_selected = false |
||||||
|
lettings_log_address_manually_entered.save!(validate: false) |
||||||
|
|
||||||
|
sales_log_address_manually_entered.manual_address_entry_selected = false |
||||||
|
sales_log_address_manually_entered.save!(validate: false) |
||||||
|
end |
||||||
|
|
||||||
|
it "updates logs with an address manually entered" do |
||||||
|
expect(lettings_log_address_manually_entered.manual_address_entry_selected).to be false |
||||||
|
expect(lettings_log_address_manually_entered.address_line1).to eq("1 Test Street") |
||||||
|
expect(lettings_log_address_manually_entered.address_line2).to eq("Testville") |
||||||
|
expect(lettings_log_address_manually_entered.town_or_city).to eq("Testford") |
||||||
|
expect(lettings_log_address_manually_entered.postcode_full).to eq("SW1 1AA") |
||||||
|
|
||||||
|
expect(sales_log_address_manually_entered.manual_address_entry_selected).to be false |
||||||
|
expect(sales_log_address_manually_entered.address_line1).to eq("1 Test Street") |
||||||
|
expect(sales_log_address_manually_entered.address_line2).to eq("Testville") |
||||||
|
expect(sales_log_address_manually_entered.town_or_city).to eq("Testford") |
||||||
|
expect(sales_log_address_manually_entered.postcode_full).to eq("SW1 1AA") |
||||||
|
|
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_address_manually_entered.reload |
||||||
|
sales_log_address_manually_entered.reload |
||||||
|
|
||||||
|
expect(lettings_log_address_manually_entered.manual_address_entry_selected).to be true |
||||||
|
expect(lettings_log_address_manually_entered.address_line1).to eq("1 Test Street") |
||||||
|
expect(lettings_log_address_manually_entered.address_line2).to eq("Testville") |
||||||
|
expect(lettings_log_address_manually_entered.town_or_city).to eq("Testford") |
||||||
|
expect(lettings_log_address_manually_entered.postcode_full).to eq("SW1 1AA") |
||||||
|
|
||||||
|
expect(sales_log_address_manually_entered.manual_address_entry_selected).to be true |
||||||
|
expect(sales_log_address_manually_entered.address_line1).to eq("1 Test Street") |
||||||
|
expect(sales_log_address_manually_entered.address_line2).to eq("Testville") |
||||||
|
expect(sales_log_address_manually_entered.town_or_city).to eq("Testford") |
||||||
|
expect(sales_log_address_manually_entered.postcode_full).to eq("SW1 1AA") |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "bulk_update:update_postcode_full_preexisting_manual_entry_logs" do |
||||||
|
let(:task) { Rake::Task["bulk_update:update_postcode_full_preexisting_manual_entry_logs"] } |
||||||
|
|
||||||
|
let(:lettings_log_to_fix) do |
||||||
|
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), updated_at: Time.zone.parse("2025-03-19 16:30:00")) |
||||||
|
end |
||||||
|
|
||||||
|
let(:bu_lettings_log_to_fix) do |
||||||
|
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), creation_method: "bulk upload", updated_at: Time.zone.parse("2025-03-19 16:30:00")) |
||||||
|
end |
||||||
|
|
||||||
|
let(:lettings_log_not_to_fix) do |
||||||
|
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), updated_at: Time.zone.parse("2025-03-19 15:30:00")) |
||||||
|
end |
||||||
|
|
||||||
|
before do |
||||||
|
lettings_log_to_fix.manual_address_entry_selected = true |
||||||
|
lettings_log_to_fix.address_line1 = "1 Test Street" |
||||||
|
lettings_log_to_fix.address_line2 = "Testville" |
||||||
|
lettings_log_to_fix.town_or_city = "Testford" |
||||||
|
lettings_log_to_fix.postcode_full = nil |
||||||
|
lettings_log_to_fix.address_line1_input = "1 Test Street" |
||||||
|
lettings_log_to_fix.postcode_full_input = "SW1 2BB" |
||||||
|
lettings_log_to_fix.save!(validate: false) |
||||||
|
|
||||||
|
bu_lettings_log_to_fix.manual_address_entry_selected = true |
||||||
|
bu_lettings_log_to_fix.address_line1 = "1 Test Street" |
||||||
|
bu_lettings_log_to_fix.address_line2 = "Testville" |
||||||
|
bu_lettings_log_to_fix.town_or_city = "Testford" |
||||||
|
bu_lettings_log_to_fix.postcode_full = nil |
||||||
|
bu_lettings_log_to_fix.address_line1_as_entered = "1 Test Street" |
||||||
|
bu_lettings_log_to_fix.postcode_full_as_entered = "SW1 2BB" |
||||||
|
bu_lettings_log_to_fix.save!(validate: false) |
||||||
|
|
||||||
|
lettings_log_not_to_fix.postcode_full = nil |
||||||
|
lettings_log_not_to_fix.save!(validate: false) |
||||||
|
end |
||||||
|
|
||||||
|
context "when running the task" do |
||||||
|
it "updates logs that meet the criteria" do |
||||||
|
expect(lettings_log_to_fix.postcode_full).to be_nil |
||||||
|
expect(lettings_log_to_fix.address_line1).to eq("1 Test Street") |
||||||
|
expect(lettings_log_to_fix.address_line2).to eq("Testville") |
||||||
|
expect(lettings_log_to_fix.town_or_city).to eq("Testford") |
||||||
|
expect(lettings_log_to_fix.address_line1_input).to eq("1 Test Street") |
||||||
|
expect(lettings_log_to_fix.postcode_full_input).to eq("SW1 2BB") |
||||||
|
|
||||||
|
expect(bu_lettings_log_to_fix.postcode_full).to be_nil |
||||||
|
expect(bu_lettings_log_to_fix.address_line1_input).to be_nil |
||||||
|
expect(bu_lettings_log_to_fix.address_line1).to eq("1 Test Street") |
||||||
|
expect(bu_lettings_log_to_fix.address_line2).to eq("Testville") |
||||||
|
expect(bu_lettings_log_to_fix.town_or_city).to eq("Testford") |
||||||
|
expect(bu_lettings_log_to_fix.address_line1_as_entered).to eq("1 Test Street") |
||||||
|
expect(bu_lettings_log_to_fix.postcode_full_as_entered).to eq("SW1 2BB") |
||||||
|
|
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_to_fix.reload |
||||||
|
bu_lettings_log_to_fix.reload |
||||||
|
|
||||||
|
expect(lettings_log_to_fix.postcode_full).to eq(lettings_log_to_fix.postcode_full_input) |
||||||
|
expect(lettings_log_to_fix.postcode_full).to eq("SW1 2BB") |
||||||
|
expect(bu_lettings_log_to_fix.postcode_full).to eq(bu_lettings_log_to_fix.postcode_full_as_entered) |
||||||
|
expect(bu_lettings_log_to_fix.postcode_full).to eq("SW1 2BB") |
||||||
|
end |
||||||
|
|
||||||
|
it "does not update logs that do not meet the criteria" do |
||||||
|
task.invoke |
||||||
|
|
||||||
|
lettings_log_not_to_fix.reload |
||||||
|
|
||||||
|
expect(lettings_log_not_to_fix.postcode_full).to be_nil |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,425 @@ |
|||||||
|
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!(/\{owning_org_id\}/, sales_log["owning_organisation_id"].to_s) |
||||||
|
export_template.sub!(/\{owning_org_name\}/, sales_log.owning_organisation.name.to_s) |
||||||
|
export_template.sub!(/\{managing_org_id\}/, sales_log["managing_organisation_id"].to_s) |
||||||
|
export_template.sub!(/\{managing_org_name\}/, sales_log.managing_organisation.name.to_s) |
||||||
|
export_template.sub!(/\{assigned_to_id\}/, sales_log["assigned_to_id"].to_s) |
||||||
|
export_template.sub!(/\{assigned_to_email\}/, sales_log.assigned_to&.email.to_s) |
||||||
|
export_template.sub!(/\{created_by_id\}/, sales_log["created_by_id"].to_s) |
||||||
|
export_template.sub!(/\{created_by_email\}/, sales_log.created_by&.email.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!("<DUPLICATESET/>", "<DUPLICATESET>123</DUPLICATESET>") |
||||||
|
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 |
||||||
|
|
||||||
|
context "when exporting various fees, correctly maps the values" do |
||||||
|
context "with discounted ownership and mscharge" do |
||||||
|
let!(:sales_log) { FactoryBot.create(:sales_log, :export, mscharge: 123) } |
||||||
|
|
||||||
|
def replace_mscharge_value(export_file) |
||||||
|
export_file.sub!("<MSCHARGE>100.0</MSCHARGE>", "<MSCHARGE>123.0</MSCHARGE>") |
||||||
|
end |
||||||
|
|
||||||
|
it "exports mscharge fields as hasmscharge and mscharge" do |
||||||
|
expected_content = replace_entity_ids(sales_log, xml_export_file.read) |
||||||
|
expected_content = replace_mscharge_value(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 "with shared ownership and mscharge" do |
||||||
|
let!(:sales_log) { FactoryBot.create(:sales_log, :export, ownershipsch: 1, staircase: 2, type: 30, mscharge: 321, has_management_fee: 1, management_fee: 222) } |
||||||
|
|
||||||
|
def replace_mscharge_and_shared_ownership_values(export_file) |
||||||
|
export_file.sub!("<HASSERVICECHARGES/>", "<HASSERVICECHARGES>1</HASSERVICECHARGES>") |
||||||
|
export_file.sub!("<SERVICECHARGES/>", "<SERVICECHARGES>321.0</SERVICECHARGES>") |
||||||
|
export_file.sub!("<HASESTATEFEE/>", "<HASESTATEFEE>1</HASESTATEFEE>") |
||||||
|
export_file.sub!("<ESTATEFEE/>", "<ESTATEFEE>222.0</ESTATEFEE>") |
||||||
|
export_file.sub!("<MSCHARGE>100.0</MSCHARGE>", "<MSCHARGE/>") |
||||||
|
export_file.sub!("<HASMSCHARGE>1</HASMSCHARGE>", "<HASMSCHARGE/>") |
||||||
|
|
||||||
|
export_file.sub!("<TYPE>8</TYPE>", "<TYPE>30</TYPE>") |
||||||
|
export_file.sub!("<STAIRCASE/>", "<STAIRCASE>2</STAIRCASE>") |
||||||
|
export_file.sub!("<GRANT>10000.0</GRANT>", "<GRANT/>") |
||||||
|
export_file.sub!("<PPCODENK>0</PPCODENK>", "<PPCODENK>1</PPCODENK>") |
||||||
|
export_file.sub!("<PPOSTC1>SW1A</PPOSTC1>", "<PPOSTC1/>") |
||||||
|
export_file.sub!("<PPOSTC2>1AA</PPOSTC2>", "<PPOSTC2/>") |
||||||
|
export_file.sub!("<PREVLOC>E09000033</PREVLOC>", "<PREVLOC/>") |
||||||
|
export_file.sub!("<EXTRABOR>1</EXTRABOR>", "<EXTRABOR/>") |
||||||
|
export_file.sub!("<OWNERSHIP>2</OWNERSHIP>", "<OWNERSHIP>1</OWNERSHIP>") |
||||||
|
export_file.sub!("<PREVIOUSLAKNOWN>1</PREVIOUSLAKNOWN>", "<PREVIOUSLAKNOWN>0</PREVIOUSLAKNOWN>") |
||||||
|
export_file.sub!("<PREVLOCNAME>Westminster</PREVLOCNAME>", "<PREVLOCNAME/>") |
||||||
|
end |
||||||
|
|
||||||
|
it "exports mscharge fields as hasmscharge and mscharge" do |
||||||
|
expected_content = replace_entity_ids(sales_log, xml_export_file.read) |
||||||
|
expected_content = replace_mscharge_and_shared_ownership_values(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 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue