Keeping up with a busy table
Acompanhando o movimento da mesa
Selling art at an event means switching between conversations, payments, and questions about what's still available. Stock is easy to lose track of when you're also trying to show people your work.
I built Artist Alley POS to bring those tasks together. It's a point-of-sale application for artists, with inventory, checkout, and event reports in one place. Sales and stock share the same record instead of living in separate spreadsheets.
Each studio can configure its branding, currency, language, and appearance. Products have prices, costs, stock levels, and optional variations. At the table, the flow is familiar: find an item, add it to the cart, apply a bundle or discount, and record the payment.
Vender arte em um evento significa alternar entre conversas, pagamentos e perguntas sobre o que ainda está disponível. É fácil perder a conta do estoque quando você também está tentando apresentar seu trabalho.
Criei o Artist Alley POS para reunir essas tarefas. É um sistema de ponto de venda para artistas, com estoque, vendas e relatórios de eventos no mesmo lugar. As vendas e o estoque usam os mesmos registros, em vez de ficarem em planilhas separadas.
Cada estúdio pode configurar sua identidade visual, moeda, idioma e aparência da aplicação. Os produtos têm preços, custos, quantidades em estoque e variações opcionais. Na mesa, o processo é familiar: encontrar o item, adicionar ao carrinho, aplicar uma oferta de combo ou desconto e registrar o pagamento.
When the connection drops
Quando a conexão cai
A card payment can be approved before the application finishes saving the sale. If the connection drops between those steps, the system needs to handle that without losing the record or creating a duplicate.
The application saves sales and stock adjustments locally in the browser's IndexedDB database before sending them to the server. Pending actions can be retried when the service is reachable again. Each sale has an identifier so the server can recognize a repeat submission rather than count it as another sale.
That protection has limits. The initial setup and product catalog still need a connection, so this isn't a fully offline system.
React and TypeScript handle the interface, while FastAPI confirms sales, updates inventory, and produces reports. On Android, Capacitor packages the application, and a native Java bridge connects it to a SumUp card reader.
Um pagamento no cartão pode ser aprovado antes de a aplicação terminar de salvar a venda. Se a conexão cair entre essas etapas, o sistema precisa lidar com isso sem perder o registro nem criar uma venda duplicada.
A aplicação salva as vendas e os ajustes de estoque localmente no IndexedDB, um banco de dados do navegador, antes de enviá-los ao servidor. As ações pendentes podem ser reenviadas quando o serviço estiver acessível novamente. Cada venda tem um identificador que permite ao servidor reconhecer um reenvio, em vez de contabilizá-lo como outra venda.
Essa proteção tem limites. A configuração inicial e o catálogo de produtos ainda precisam de conexão, então o sistema não funciona inteiramente offline.
React e TypeScript cuidam da interface, enquanto o FastAPI confirma as vendas, atualiza o estoque e gera os relatórios. No Android, o Capacitor empacota a aplicação, e uma integração nativa em Java faz a comunicação com a maquininha da SumUp.
The local outbox sends pending actions to the API and database. Card reader approval returns to the interface.A fila local envia ações pendentes à API e ao banco. A aprovação do leitor de cartão retorna à interface.
- POS interfaceInterface do POSReact · inventory and checkoutReact · estoque e vendas
- Pending actionsAções pendentesIndexedDB · sales and stock outboxIndexedDB · vendas e estoque
- Application APIAPI da aplicaçãoFastAPI · confirmation and reportsFastAPI · confirmação e relatórios
- Artist’s dataDados do artistaSQLite · separate client instanceSQLite · instância por cliente
- Card readerLeitor de cartãoAndroid · Capacitor and SumUp SDKAndroid · Capacitor e SDK SumUp
- Database backupBackup do bancoLitestream · S3-compatible storageLitestream · armazenamento S3
A separate installation for each artist
Uma instalação separada para cada artista
Each client has a separate application instance and SQLite database. One artist's records stay outside another artist's environment.
Docker packages the interface and Python application together. Cloudflare Tunnel provides access to the service, with Cloudflare Access handling authentication. The deployment configuration also includes Litestream replication to S3-compatible storage.
This keeps the installation relatively small and portable, but it doesn't remove the work of looking after the data. Database migrations run at startup, so an application update may also change the database structure. Backups and recovery still need attention.
Cada cliente tem sua própria instância da aplicação e seu próprio banco SQLite. Os registros de um artista ficam fora do ambiente de outro.
O Docker empacota a interface e a aplicação Python juntas. O Cloudflare Tunnel permite o acesso ao serviço, com o Cloudflare Access cuidando da autenticação. A configuração de implantação também inclui replicação com Litestream para um armazenamento compatível com S3.
Isso mantém a instalação relativamente pequena e fácil de mover, mas não elimina o cuidado com os dados. As migrações do banco são executadas na inicialização, então uma atualização da aplicação também pode alterar a estrutura do banco. Backups e recuperação continuam precisando de atenção.
Making sense of the event
Entendendo o resultado do evento
Once the table is packed away, sales totals only tell part of the story. The reports bring together sales, product costs, payment fees, and event expenses to help the artist understand how the event went.
Each sale keeps a snapshot of its prices, costs, and promotions. Changing a product later doesn't change the record of what was sold. CSV and PDF exports let artists use those records outside the application too.
What interests me most about this project is getting those details to agree. A payment approved by the reader, a sale saved on the device, and the figures in a report all describe the same transaction. Making sure they stay consistent is a large part of the work behind an interface that needs to be easy to use at a busy table.
Depois de desmontar a mesa, o total de vendas conta apenas parte da história. Os relatórios reúnem vendas, custos dos produtos, taxas de pagamento e despesas do evento para ajudar o artista a entender como foi o resultado.
Cada venda preserva os preços, custos e promoções daquele momento. Alterar um produto depois não muda o registro do que foi vendido. As exportações em CSV e PDF permitem usar esses dados fora da aplicação também.
O que mais me interessa nesse projeto é fazer esses detalhes baterem. Um pagamento aprovado na maquininha, uma venda salva no dispositivo e os números de um relatório descrevem a mesma transação. Manter tudo isso consistente é boa parte do trabalho por trás de uma interface que precisa ser fácil de usar quando a mesa está cheia.