invoiceninja / dockerfiles

Docker files for Invoice Ninja
https://hub.docker.com/r/invoiceninja/invoiceninja
GNU General Public License v2.0
412 stars 267 forks source link

Issue viewing or downloading documents on invoices (generated from offers?) #627

Open ret1 opened 4 days ago

ret1 commented 4 days ago

Hi everyone!

Version v5.10.44 Environment Docker

When I attempt to download or view a document attached to an invoice, I receive an error message (see screenshot): Screenshot 2024-11-10 at 12 30 58 Screenshot 2024-11-10 at 12 30 25

The following error appears in the logs:

[2024-11-10 11:26:33] production.ERROR: Unable to retrieve the file_size for file at location: var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf. {“userId”:1,“exception”:"[object] (League\Flysystem\UnableToRetrieveMetadata(code: 0): Unable to retrieve the file_size for file at location: var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf. at /var/www/app/vendor/league/flysystem/src/UnableToRetrieveMetadata.php:49)
[stacktrace]
#0 /var/www/app/vendor/league/flysystem/src/UnableToRetrieveMetadata.php(39): League\Flysystem\UnableToRetrieveMetadata::create(‘var/www/app/pub…’, ‘file_size’, ‘’, NULL)
#1 /var/www/app/vendor/league/flysystem-local/LocalFilesystemAdapter.php(445): League\Flysystem\UnableToRetrieveMetadata::fileSize(‘var/www/app/pub…’, ‘’)
#2 /var/www/app/vendor/league/flysystem/src/Filesystem.php(166): League\Flysystem\Local\LocalFilesystemAdapter->fileSize(‘var/www/app/pub…’)
#3 /var/www/app/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php(597): League\Flysystem\Filesystem->fileSize(‘/var/www/app/pu…’)
#4 /var/www/app/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php(301): Illuminate\Filesystem\FilesystemAdapter->size(‘/var/www/app/pu…’)
#5 /var/www/app/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php(350): Illuminate\Filesystem\FilesystemAdapter->response(‘/var/www/app/pu…’, ‘Angebot-2024-00…’, Array, ‘attachment’)
#6 /var/www/app/app/Http/Controllers/ClientPortal/DocumentController.php(69): Illuminate\Filesystem\FilesystemAdapter->download(‘/var/www/app/pu…’, ‘Angebot-2024-00…’, Array)
#7 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\Http\Controllers\ClientPortal\DocumentController->publicDownload(‘4a0UELp58h2InMI…’)
#8 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\Routing\Controller->callAction(‘publicDownload’, Array)
#9 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Tracing/Routing/TracingControllerDispatcherTracing.php(21): Illuminate\Routing\ControllerDispatcher->dispatch(Object(Illuminate\Routing\Route), Object(App\Http\Controllers\ClientPortal\DocumentController), ‘publicDownload’)
#10 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Tracing/Routing/TracingRoutingDispatcher.php(18): Sentry\Laravel\Tracing\Routing\TracingControllerDispatcherTracing->Sentry\Laravel\Tracing\Routing\{closure}()
#11 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Tracing/Routing/TracingControllerDispatcherTracing.php(20): Sentry\Laravel\Tracing\Routing\TracingRoutingDispatcher->wrapRouteDispatch(Object(Closure), Object(Illuminate\Routing\Route))
#12 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Route.php(264): Sentry\Laravel\Tracing\Routing\TracingControllerDispatcherTracing->dispatch(Object(Illuminate\Routing\Route), Object(App\Http\Controllers\ClientPortal\DocumentController), ‘publicDownload’)
#13 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Route.php(210): Illuminate\Routing\Route->runController()
#14 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\Routing\Route->run()
#15 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\Routing\Router->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#16 /var/www/app/app/Http/Middleware/QueryLogging.php(39): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#17 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): App\Http\Middleware\QueryLogging->handle(Object(Illuminate\Http\Request), Object(Closure))
#18 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(51): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#19 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Routing\Middleware\SubstituteBindings->handle(Object(Illuminate\Http\Request), Object(Closure))
#20 /var/www/app/app/Http/Middleware/TokenAuth.php(102): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#21 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): App\Http\Middleware\TokenAuth->handle(Object(Illuminate\Http\Request), Object(Closure))
#22 /var/www/app/app/Http/Middleware/SetDb.php(40): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#23 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): App\Http\Middleware\SetDb->handle(Object(Illuminate\Http\Request), Object(Closure))
#24 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#25 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Foundation\Http\Middleware\VerifyCsrfToken->handle(Object(Illuminate\Http\Request), Object(Closure))
#26 /var/www/app/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#27 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\View\Middleware\ShareErrorsFromSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#28 /var/www/app/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#29 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse->handle(Object(Illuminate\Http\Request), Object(Closure))
#30 /var/www/app/app/Http/Middleware/SessionDomains.php(30): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#31 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): App\Http\Middleware\SessionDomains->handle(Object(Illuminate\Http\Request), Object(Closure))
#32 /var/www/app/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#33 /var/www/app/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\Session\Middleware\StartSession->handleStatefulRequest(Object(Illuminate\Http\Request), Object(Illuminate\Session\Store), Object(Closure))
#34 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Session\Middleware\StartSession->handle(Object(Illuminate\Http\Request), Object(Closure))
#35 /var/www/app/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#36 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Cookie\Middleware\EncryptCookies->handle(Object(Illuminate\Http\Request), Object(Closure))
#37 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#38 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#39 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\Routing\Router->runRouteWithinStack(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))
#40 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\Routing\Router->runRoute(Object(Illuminate\Http\Request), Object(Illuminate\Routing\Route))
#41 /var/www/app/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#42 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#43 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http\{closure}(Object(Illuminate\Http\Request))
#44 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Http/FlushEventsMiddleware.php(13): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#45 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Sentry\Laravel\Http\FlushEventsMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#46 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Http/SetRequestIpMiddleware.php(45): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#47 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Sentry\Laravel\Http\SetRequestIpMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#48 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Http/SetRequestMiddleware.php(31): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#49 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Sentry\Laravel\Http\SetRequestMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#50 /var/www/app/vendor/livewire/livewire/src/Features/SupportDisablingBackButtonCache/DisableBackButtonCacheMiddleware.php(19): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#51 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Livewire\Features\SupportDisablingBackButtonCache\DisableBackButtonCacheMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#52 /var/www/app/app/Http/Middleware/Cors.php(24): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#53 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): App\Http\Middleware\Cors->handle(Object(Illuminate\Http\Request), Object(Closure))
#54 /var/www/app/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(58): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#55 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Http\Middleware\TrustProxies->handle(Object(Illuminate\Http\Request), Object(Closure))
#56 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#57 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#58 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull->handle(Object(Illuminate\Http\Request), Object(Closure))
#59 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#60 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\Foundation\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Closure))
#61 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Foundation\Http\Middleware\TrimStrings->handle(Object(Illuminate\Http\Request), Object(Closure))
#62 /var/www/app/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#63 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Http\Middleware\ValidatePostSize->handle(Object(Illuminate\Http\Request), Object(Closure))
#64 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#65 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance->handle(Object(Illuminate\Http\Request), Object(Closure))
#66 /var/www/app/vendor/sentry/sentry-laravel/src/Sentry/Laravel/Tracing/Middleware.php(79): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#67 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Sentry\Laravel\Tracing\Middleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#68 /var/www/app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#69 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#70 /var/www/app/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#71 /var/www/app/public/index.php(56): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#72 {main}
"}

However, the file mentioned in the log does exist and is readable:


root@server:~/dockerfiles/invoiceninja/docker/app# file public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf
public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf: PDF document, version 1.7, 2 page(s)

root@server:~/dockerfiles/invoiceninja# docker ps | grep invoiceninja
578d013188c8   nginx                           "/docker-entrypoint.…"   About a minute ago   Up About a minute      80/tcp                                                                      invoiceninja-server-1
5d306a5eb071   invoiceninja/invoiceninja:5     "docker-entrypoint s…"   About a minute ago   Up About a minute      9000/tcp                                                                    invoiceninja-app-1
69882a55b879   mysql:8                         "docker-entrypoint.s…"   About a minute ago   Up About a minute      3306/tcp, 33060/tcp                                                         invoiceninja-db-1

root@server:~/dockerfiles/invoiceninja/docker/app# docker exec -it invoiceninja-server-1 /bin/bash
root@a9acbda66ffd:/# ls -la var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf
-rw-rw-r-- 1 1500 1500 109442 Nov  5 16:42 var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf

My assumption: I generated the affected invoice from an offer. During this process, the document attached to the offer is “copied” to the invoice. When I try to open the same document under the original offer, it works without issue. Could it be that this problem only occurs when an invoice is created from an offer that already has attached documents?

ret1 commented 4 days ago

https://forum.invoiceninja.com/t/issue-viewing-or-downloading-documents-on-invoices-generated-from-offers/17302

turbo124 commented 4 days ago

I cannot recreate this on my docker instance, I would suggest exec sh into the container and see if the file actually exists in that location.

ret1 commented 3 days ago

hello @turbo124 !

The filepath referenced in the log appears to be relative (var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf). However, this file does indeed exist in the Nginx container at the following absolute path:

/var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/4a0UELp58h2InMIIQ0VhTZPsqVriLWpG.pdf

turbo124 commented 3 days ago

@ret1

Are you using a custom dockerfile, or the standard one as included in invoiceninja/dockerfiles?

ret1 commented 2 days ago

Hello @turbo124 ! I've made some adaptions regarding the network settings and the restart behaviour, but the volume config is exactly the same. here is my docker-compose file:


  server:
    image: nginx
    restart: unless-stopped
    env_file: env
    volumes:
      # Vhost configuration
      #- ./config/caddy/Caddyfile:/etc/caddy/Caddyfiledocker-com
      - ./config/nginx/in-vhost.conf:/etc/nginx/conf.d/in-vhost.conf:ro
      - ./docker/app/public:/var/www/app/public:ro
    depends_on:
      - app
    # Run webserver nginx on port 80
    # Feel free to modify depending what port is already occupied
#    ports:
#      - "80:80"
#      #- "443:443"
    networks:
      haproxy-network-invoiceninja:
        ipv4_address: 10.0.10.101
        ipv6_address: fd4d:6169:6c63:10::101
#    extra_hosts:
#      - "in5.localhost:192.168.0.124 " #host and ip

  app:
    image: invoiceninja/invoiceninja:5
    env_file: env
    restart: unless-stopped
    volumes:
      - ./config/hosts:/etc/hosts:ro
      - ./docker/app/public:/var/www/app/public:rw,delegated
      - ./docker/app/storage:/var/www/app/storage:rw,delegated
      - ./config/php/php.ini:/usr/local/etc/php/php.ini
      - ./config/php/php-cli.ini:/usr/local/etc/php/php-cli.ini

    depends_on:
      - db
    networks:
      haproxy-network-invoiceninja:
        ipv4_address: 10.0.10.102
        ipv6_address: fd4d:6169:6c63:10::102
#    extra_hosts:
#      - "in5.localhost:192.168.0.124 " #host and ip

  db:
    image: mysql:8
#    When running on ARM64 use MariaDB instead of MySQL
#    image: mariadb:10.4
#    For auto DB backups comment out image and use the build block below
#    build:
#      context: ./config/mysql
    restart: unless-stopped
    env_file: env
    volumes:
      - ./docker/mysql/data:/var/lib/mysql:rw,delegated

      # remove comments for next 4 lines if you want auto sql backups
      #- ./docker/mysql/bak:/backups:rw
      #- ./config/mysql/backup-script:/etc/cron.daily/daily:ro
      #- ./config/mysql/backup-script:/etc/cron.weekly/weekly:ro
      #- ./config/mysql/backup-script:/etc/cron.monthly/monthly:ro
    networks:
      haproxy-network-invoiceninja:
        ipv4_address: 10.0.10.103
        ipv6_address: fd4d:6169:6c63:10::103
#    extra_hosts:
#      - "in5.localhost:192.168.0.124 " #host and ip

  # THIS IS ONLY A VALID CONFIGURATION FOR IN 4. DO NOT USE FOR IN 5.
  # cron:
  #   image: invoiceninja/invoiceninja:alpine-4
  #   volumes:
      # - ./docker/app/public:/var/www/app/public:rw,delegated
      # - ./docker/app/storage:/var/www/app/storage:rw,delegated
      # - ./docker/app/public/logo:/var/www/app/public/logo:rw,delegated
  #   entrypoint: |
  #     /bin/sh -c 'sh -s <<EOF
  #     trap "break;exit" SIGHUP SIGINT SIGTERM
  #     sleep 300s
  #     while /bin/true; do
  #       ./artisan ninja:send-invoices
  #       ./artisan ninja:send-reminders
  #       sleep 1d
  #     done
  #     EOF'
  #   networks:
  #     - invoiceninja
  #

networks:
  haproxy-network-invoiceninja:
    external: true
turbo124 commented 2 days ago

make sure you have IS_DOCKER=true in your .env file

ret1 commented 2 days ago

The IS_DOCKER=true setting was not enabled, but this doesn’t appear to be the cause of the issue (including for future conversions of quotes to invoices). After further investigation, I identified additional details.

When I convert a quote to an invoice that already has an attached document, the URL field for the invoice in the database is stored as follows:

/var/www/app/public/storage/xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/PHQZha0xYjJvIQ09xWM3Cf3MXcTjsBVL.pdf

However, if I attach a document to the invoice after converting the quote, the URL field appears as:

xKdK81rHaAnqLuYBQ9cZDW82DJOWJv4x/documents/lqLdh7QciKhEgfKGMQYbM6Zk5ndVCeBBr6JMRMIf.pdf

Upon reviewing all records in the documents table, it seems that only entries with documentable_type = invoice contain the fully qualified path (starting with /var/www...). Furthermore, only these entries cannot be accessed via the Invoice Ninja web interface.

After manually removing the /var/www/app/public/storage/ prefix from all affected records, I was able to successfully open all attached documents.

This leaves the question of why and where the incorrect path is being generated and written to the database.