mirror of
https://github.com/we-promise/sure.git
synced 2026-05-25 05:24:57 +00:00
feat(exports): add attachment manifest (#1728)
* feat(exports): add attachment manifest * fix(exports): include split parent receipts in manifest
This commit is contained in:
@@ -29,6 +29,10 @@ class Family::DataExporter
|
||||
zipfile.put_next_entry("rules.csv")
|
||||
zipfile.write generate_rules_csv
|
||||
|
||||
# Add attachment manifest metadata. Binary file payloads are not included.
|
||||
zipfile.put_next_entry("attachments.json")
|
||||
zipfile.write generate_attachments_manifest
|
||||
|
||||
# Add all.ndjson
|
||||
zipfile.put_next_entry("all.ndjson")
|
||||
zipfile.write generate_ndjson
|
||||
@@ -138,6 +142,69 @@ class Family::DataExporter
|
||||
end
|
||||
end
|
||||
|
||||
def generate_attachments_manifest
|
||||
{
|
||||
version: 1,
|
||||
binary_included: false,
|
||||
attachments: attachment_manifest_items
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def attachment_manifest_items
|
||||
(transaction_attachment_manifest_items + family_document_attachment_manifest_items)
|
||||
.sort_by { |item| [ item[:record_type], item[:record_id].to_s, item[:filename].to_s, item[:id].to_s ] }
|
||||
end
|
||||
|
||||
def transaction_attachment_manifest_items
|
||||
@family.transactions
|
||||
.with_attached_attachments
|
||||
.includes(:attachments_attachments, entry: :account)
|
||||
.flat_map do |transaction|
|
||||
transaction.attachments.map do |attachment|
|
||||
attachment_manifest_item(
|
||||
attachment,
|
||||
record_type: "Transaction",
|
||||
record_id: transaction.id,
|
||||
extra: {
|
||||
entry_id: transaction.entry.id,
|
||||
account_id: transaction.entry.account_id
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def family_document_attachment_manifest_items
|
||||
@family.family_documents.with_attached_file.filter_map do |document|
|
||||
next unless document.file.attached?
|
||||
|
||||
attachment_manifest_item(
|
||||
document.file.attachment,
|
||||
record_type: "FamilyDocument",
|
||||
record_id: document.id,
|
||||
extra: {
|
||||
status: document.status
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_manifest_item(attachment, record_type:, record_id:, extra: {})
|
||||
blob = attachment.blob
|
||||
{
|
||||
id: attachment.id,
|
||||
record_type: record_type,
|
||||
record_id: record_id,
|
||||
name: attachment.name,
|
||||
filename: blob.filename.to_s,
|
||||
content_type: blob.content_type,
|
||||
byte_size: blob.byte_size,
|
||||
checksum: blob.checksum,
|
||||
binary_included: false,
|
||||
created_at: attachment.created_at
|
||||
}.merge(extra)
|
||||
end
|
||||
|
||||
def generate_ndjson
|
||||
lines = []
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class Family::DataExporterTest < ActiveSupport::TestCase
|
||||
assert zip_data.is_a?(StringIO)
|
||||
|
||||
# Check that the zip contains all expected files
|
||||
expected_files = [ "accounts.csv", "transactions.csv", "trades.csv", "categories.csv", "rules.csv", "all.ndjson" ]
|
||||
expected_files = [ "accounts.csv", "transactions.csv", "trades.csv", "categories.csv", "rules.csv", "attachments.json", "all.ndjson" ]
|
||||
|
||||
Zip::File.open_buffer(zip_data) do |zip|
|
||||
actual_files = zip.entries.map(&:name)
|
||||
@@ -55,6 +55,109 @@ class Family::DataExporterTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "exports attachment manifest metadata without binary payloads" do
|
||||
entry = @account.entries.create!(
|
||||
name: "Receipt Transaction",
|
||||
amount: 12.34,
|
||||
currency: "USD",
|
||||
date: Date.current,
|
||||
entryable: Transaction.new
|
||||
)
|
||||
transaction = entry.transaction
|
||||
transaction.attachments.attach(
|
||||
io: StringIO.new("receipt bytes"),
|
||||
filename: "receipt.pdf",
|
||||
content_type: "application/pdf"
|
||||
)
|
||||
|
||||
family_document = @family.family_documents.create!(
|
||||
filename: "statement.pdf",
|
||||
status: "ready"
|
||||
)
|
||||
family_document.file.attach(
|
||||
io: StringIO.new("statement bytes"),
|
||||
filename: "statement.pdf",
|
||||
content_type: "application/pdf"
|
||||
)
|
||||
|
||||
other_account = @other_family.accounts.create!(
|
||||
name: "Other Attachment Account",
|
||||
accountable: Depository.new,
|
||||
balance: 0,
|
||||
currency: "USD"
|
||||
)
|
||||
other_entry = other_account.entries.create!(
|
||||
name: "Other Receipt",
|
||||
amount: 1,
|
||||
currency: "USD",
|
||||
date: Date.current,
|
||||
entryable: Transaction.new
|
||||
)
|
||||
other_entry.transaction.attachments.attach(
|
||||
io: StringIO.new("other bytes"),
|
||||
filename: "other-receipt.pdf",
|
||||
content_type: "application/pdf"
|
||||
)
|
||||
|
||||
zip_data = @exporter.generate_export
|
||||
|
||||
Zip::File.open_buffer(zip_data) do |zip|
|
||||
manifest = JSON.parse(zip.read("attachments.json"))
|
||||
attachments = manifest["attachments"]
|
||||
filenames = attachments.map { |attachment| attachment["filename"] }
|
||||
|
||||
assert_equal 1, manifest["version"]
|
||||
assert_equal false, manifest["binary_included"]
|
||||
assert_includes filenames, "receipt.pdf"
|
||||
assert_includes filenames, "statement.pdf"
|
||||
refute_includes filenames, "other-receipt.pdf"
|
||||
|
||||
transaction_item = attachments.find { |attachment| attachment["record_type"] == "Transaction" }
|
||||
assert_equal transaction.id, transaction_item["record_id"]
|
||||
assert_equal entry.id, transaction_item["entry_id"]
|
||||
assert_equal @account.id, transaction_item["account_id"]
|
||||
assert_equal "attachments", transaction_item["name"]
|
||||
assert_equal "application/pdf", transaction_item["content_type"]
|
||||
assert_equal false, transaction_item["binary_included"]
|
||||
|
||||
document_item = attachments.find { |attachment| attachment["record_type"] == "FamilyDocument" }
|
||||
assert_equal family_document.id, document_item["record_id"]
|
||||
assert_equal "ready", document_item["status"]
|
||||
assert_equal "file", document_item["name"]
|
||||
assert_equal false, document_item["binary_included"]
|
||||
end
|
||||
end
|
||||
|
||||
test "exports split parent receipts in attachment manifest" do
|
||||
split_parent = create_transaction_entry(
|
||||
@account,
|
||||
amount: 60,
|
||||
date: Date.parse("2024-01-25"),
|
||||
name: "Split parent receipt"
|
||||
)
|
||||
split_parent.entryable.attachments.attach(
|
||||
io: StringIO.new("split parent receipt bytes"),
|
||||
filename: "split-parent-receipt.pdf",
|
||||
content_type: "application/pdf"
|
||||
)
|
||||
split_parent.split!([
|
||||
{ name: "Split child", amount: 60, category_id: @category.id }
|
||||
])
|
||||
|
||||
zip_data = @exporter.generate_export
|
||||
|
||||
Zip::File.open_buffer(zip_data) do |zip|
|
||||
manifest = JSON.parse(zip.read("attachments.json"))
|
||||
attachment = manifest["attachments"].find { |item| item["filename"] == "split-parent-receipt.pdf" }
|
||||
|
||||
assert attachment
|
||||
assert_equal "Transaction", attachment["record_type"]
|
||||
assert_equal split_parent.entryable.id, attachment["record_id"]
|
||||
assert_equal split_parent.id, attachment["entry_id"]
|
||||
assert_equal @account.id, attachment["account_id"]
|
||||
end
|
||||
end
|
||||
|
||||
test "generates valid CSV files" do
|
||||
zip_data = @exporter.generate_export
|
||||
|
||||
|
||||
Reference in New Issue
Block a user