Skip to main content

POS requirements and certifications (aka fiscality)

Generic info​

Fiscality features are enabled by country depending on fiscality_enabled field.

An overview of the POS features per country is available in this Miro Board

To delete/reset the checkouts of a venue​

venue_id = 20172
Venue.find(venue_id).checkouts.delete_all
VenueResourceCounter.find_by(venue_id: venue_id, countable_key: 'checkout.cancellation').update(countable_value: 0)
VenueResourceCounter.find_by(venue_id: venue_id, countable_key: 'checkout.receipt').update(countable_value: 0)
VenueResourceCounter.find_by(venue_id: venue_id, countable_key: 'checkout.invoice').update(countable_value: 0)
# ... and any other venue's counter

To regenerate a checkout's PDF​

Side note: the PDF is generated using the preview field (generated on the client side).

# load the specific checkout
checkout.update_column(:pdf_path, nil)
GeneratePdfJob.perform_now(checkout)
checkout.reload.pdf_path

France​

See the NF525 documentation.

Germany​

Development​

We have a development Fiskaly account, but some entites are cleaned up every week. To re-setup a venue:

venue = Venue.find(20550)
%w[fiskaly_tss_id fiskaly_tss_state fiskaly_admin_pin fiskaly_admin_puk fiskaly_client_id fiskaly_setup_state_error].each do |key|
venue.custom_fiscal_details.delete(key)
end
venue.save!
Fiskaly::SetupTSSForVenueJob.perform_now(venue_id: venue.id)

Portugal​

  • Every month an export is generated for each venue with the fiscal details of the checkouts => SAF-T PT export
  • Every day the same report is generated to check if there are any validation errors but it's not stored (dry_run)
  • This export can also be triggered from the manager with a custom set of dates

SAF-T PT export​

To generate a SAF-T export from a specific starting date up to today:

args = { venue: Venue.find(20172), start_date: Date.parse('2021-12-01'), date: Date.current }
ctx = Venues::PtPos::GenerateFiscalArchiveInteractor.call(args)

# XML export URL - available also in the admin, example https://env4-admin.uala.it/backend/venues/16040/fiscal_archives
ctx.fiscal_archive.url if ctx.success?

# Print the XML export in console:
puts ctx.xml_export

# on errors:
ap(ctx.validation_errors) if ctx.failure?
# [
# [0] #<Nokogiri::XML::SyntaxError: 835:0: ERROR: Element '{urn:OECD:StandardAuditFile-Tax:PT_1.04_01}Invoice': Duplicate key-sequence ['FS A20172TWxU2021/00001'] in unique identity-constraint '{urn:OECD:StandardAuditFile-Tax:PT_1.04_01}InvoiceNoConstraint'.>,
# [1] #<Nokogiri::XML::SyntaxError: 884:0: ERROR: Element '{urn:OECD:StandardAuditFile-Tax:PT_1.04_01}Invoice': Duplicate key-sequence ['FS A20172TWxU2021/00002'] in unique identity-constraint '{urn:OECD:StandardAuditFile-Tax:PT_1.04_01}InvoiceNoConstraint'.>
# ]
# Here we have 2 errors at lines 835 and 884 in the Invoice XML element with the specified message.

To validate an export the AT provide a Windows tool here. It can also check the checkouts signatures if the public key is provided.

An alternative validator in PHP is this available here. It doesn't check the signatures but makes some calculation checks.

The PHP validator can be executed via Docker:

FROM php:8.0-cli
RUN apt update
RUN apt install -y git nano unzip wget
COPY . /usr/src/app
RUN cp /usr/src/app/php.ini "$PHP_INI_DIR/php.ini"
WORKDIR /usr/src/app

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN php composer.phar update

CMD ["bash"]
docker build -t saft-pt-php .
docker run -it --rm --name saft-pt-php-app saft-pt-php
# from the container
wget https://fiscal-archives.s3.eu-west-1.amazonaws.com/venues/20172/export_v20172_20211227_0000.xml
php saft4php validate ./export_v20172_20211227_0000.xml

The output should be a green line with "[OK] Validation without errors"

Signatures​

The checkouts are signed using a couple of RSA keys.

To regenerate the private and public keys:

openssl genrsa -out PrivateKey.pem 1024
openssl rsa -in PrivateKey.pem -out PublicKey.pem -outform PEM -pubout

Then we can put them in the DB (develop/production/staging):

row = CountryProvider.joins(:country).where(countries: { code: 'pt' }, provider_type: 'pt_esign', provider_name: 'private_key').last
row.client_key = <<~EOS
# paste the private key => content of PrivateKey.pem
EOS
row.client_key.strip!
row.save

row = CountryProvider.joins(:country).where(countries: { code: 'pt' }, provider_type: 'pt_esign', provider_name: 'public_key').last
row.client_key = <<~EOS
# paste the public key => content of PublicKey.pem
EOS
row.client_key.strip!
row.save

Regenerate corrupted fiscal archives (france)​

To regenerate corrupted fiscal archives, you can use this script to regenerate them. Unfortunately, for signature purpose, you need to regenerate all the next fiscal archives, starting from the first corrupted (due to the signature)

def regenerate_corrupted_fiscal_archives(venue_ids, dry_run: false)
venue_ids.each do |venue_id|
venue = Venue.find(venue_id)
next unless venue.country_code == 'fr'

start_date = venue.fiscal_archives.where(file_name: nil).first.date

if dry_run
puts "fiscal archives with ids: #{venue.fiscal_archives.where(date: start_date..Time.current.end_of_year).order(created_at: :asc).ids } will be destroyed and recreated, (venue: #{venue.id})"
else
venue.fiscal_archives.where(date: start_date..Time.current.end_of_year).destroy_all

(start_date.to_date..Time.current.to_date).map { |date| date.end_of_month.strftime("%Y-%m-%d") unless date.end_of_month.future? }.uniq.each do |date|
puts "Generating fiscal archive for venue_id: #{venue.id} and date: #{date}..."

Venues::GenerateFiscalArchiveInteractor.call(venue_id: venue.id, input_date: date)
puts 'Done'
end
end
end
end