glanzlicht / GastroOS
Verified secure showcase
500 Files 1 MB Public size 31 Redacted TypeScript · SQL · JSON · Markdown · DOCKERIGNORE · EXAMPLE

Project overview

GastroOS is a verified code showcase with 500 visible project entries across 3 top-level modules. The primary detected language is TypeScript; protected files remain visible in the structure while their contents stay private.

README

GastravaOS

GastravaOS is a modular Hospitality Operational Intelligence Platform.

The technical repository name remains GastroOS for compatibility. The product is presented publicly as GastravaOS.

Product preview

GastravaOS operations dashboard

GastravaOS is designed for hospitality operations ranging from small venues to larger multi-location organizations. The platform combines operational workflows, resilient data handling and role-specific user experiences without exposing internal complexity to operators.

What this repository demonstrates

This repository is presented as a technical portfolio showcase, not as a complete product blueprint.

It demonstrates work across:

  • backend engineering with Node.js, TypeScript, NestJS and REST/OpenAPI;
  • PostgreSQL data modeling, explicit SQL migrations and transactional consistency;
  • Flutter application architecture with Riverpod, go_router, Drift/SQLite and Dio;
  • operational workflows across hospitality domains;
  • offline-capable client foundations and synchronization patterns;
  • real-time state reconciliation and event-driven integration patterns;
  • integrations for storage, payments, fiscal/accounting systems and external platforms;
  • Docker-based development, CI, observability and runtime verification;
  • tenant-aware security, audit-oriented design and permission boundaries;
  • applied AI around deterministic domain data and human-controlled actions.

Technology stack

AreaTechnology
BackendNode.js 24 LTS, TypeScript, NestJS 11
APIREST / OpenAPI
DatabasePostgreSQL 18, Drizzle ORM, explicit SQL migrations
FrontendFlutter, Riverpod, go_router
Local dataDrift / SQLite
NetworkingDio, json_serializable
InfrastructureDocker Compose, S3-compatible storage
ArchitectureModular monolith, transactional outbox
QualityAutomated tests, CI, observability and runtime verification

Engineering approach

The codebase is organized around clear boundaries and reusable platform capabilities rather than one large feature layer.

Recurring engineering principles include:

  • deterministic and auditable domain state;
  • separation of operational truth from presentation;
  • role- and context-specific interfaces;
  • resilience under intermittent connectivity;
  • explicit integration adapters;
  • composable capabilities instead of duplicated vertical implementations;
  • controlled exposure of sensitive business, security and product logic.

Portfolio boundary

The public showcase intentionally omits detailed roadmaps, ADRs, security internals, commercial logic, AI prompts, operational playbooks and reconstructable product strategy.

Those materials remain in the private repository. The public view is optimized so technical reviewers can evaluate engineering breadth and implementation quality without receiving a reconstruction guide for the product.

Local development

The repository uses a versioned pnpm dependency graph.

corepack enable
pnpm install --frozen-lockfile

Project-specific runtime and verification details remain in the private engineering documentation.

Status

GastravaOS is under active development. The repository contains a broad implementation foundation and ongoing product work rather than a finished commercial release.

The public showcase focuses on engineering capability, architecture quality and implementation breadth while deliberately omitting core IP.

apps/backend/src/modules/accounting/application/invoice.service.tsTypeScript
import { Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { DatabaseService } from '../../../infrastructure/database/database.service';
import { OutboxService } from '../../../infrastructure/outbox/outbox.service';
import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
import {
assertCreditNoteInput,
assertInvoiceFromCheckInput,
assertInvoiceReferenceInput,
} from './invoice-input-validation';
@Injectable()
export class InvoiceService {
constructor(
private readonly database:DatabaseService,
private readonly outbox:OutboxService,
) {}
async createFromCheck(input:{
organizationId:string;locationId:string;checkId:string;
customer:any;userId?:string;dueDate?:string;notes?:string;
}) {
assertInvoiceFromCheckInput(input);
return this.database.transaction(async(tx)=>{
const check=await tx.query<any>(
`select * from checks
where id=$1 and organization_id=$2 and location_id=$3
for update`,
[input.checkId,input.organizationId,input.locationId],
);
if(!check[0]) throw new DomainConflictError('invoice.check_not_found');
const lines=await tx.query<any>(
`select ci.*,p.name as product_name
from check_items ci
left join products p on p.id=ci.product_id
where ci.check_id=$1 and ci.status<>'voided'
order by ci.created_at,ci.id`,
[input.checkId],
);
if(!lines.length) throw new DomainConflictError('invoice.no_lines');
const customerId=randomUUID();
await tx.query(
`insert into invoice_customers(
id,organization_id,name,email,vat_id,tax_number,buyer_reference,
electronic_address,address_json,locale
) values($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10)`,
[
customerId,input.organizationId,input.customer.name,input.customer.email??null,
input.customer.vatId??null,input.customer.taxNumber??null,
input.customer.buyerReference??null,input.customer.electronicAddress??null,
JSON.stringify(input.customer.address??{}),input.customer.locale??'de-DE',
],
);
let net=0n,tax=0n,gross=0n;
const normalized:any[]=[];
for(const [i,line] of lines.entries()) {
const lineGross=BigInt(line.gross_minor ?? line.total_minor ?? 0);
const lineTax=BigInt(line.tax_minor ?? 0);
const lineNet=lineGross-lineTax;
net+=lineNet; tax+=lineTax; gross+=lineGross;
normalized.push({
lineNumber:i+1,
description:line.product_name??line.description??'Item',
quantity:String(line.quantity??1),
unitCode:line.unit_code??null,
unitPriceNetMinor:lineNet.toString(),
netAmountMinor:lineNet.toString(),
taxRateBasisPoints:Number(line.tax_rate_basis_points??0),
taxAmountMinor:lineTax.toString(),
grossAmountMinor:lineGross.toString(),
sourceEntityId:line.id,
});
}
if(gross!==BigInt(check[0].gross_total_minor??gross)) {
throw new DomainConflictError('invoice.check_total_mismatch');
}
const invoiceId=randomUUID();
await tx.query(
`insert into invoices(
id,organization_id,location_id,check_id,invoice_customer_id,document_type,status,
currency_code,gross_total_minor,net_total_minor,tax_total_minor,buyer_reference,
due_date,notes,source_snapshot_json,created_by_user_id,e_invoice_status
) values($1,$2,$3,$4,$5,'invoice','draft',$6,$7,$8,$9,$10,$11,$12,$13::jsonb,$14,'not_requested')`,
[
invoiceId,input.organizationId,input.locationId,input.checkId,customerId,
check[0].currency_code,gross.toString(),net.toString(),tax.toString(),
input.customer.buyerReference??null,input.dueDate??null,input.notes??null,
JSON.stringify({
checkId:input.checkId,
checkVersion:check[0].version,
grossTotalMinor:String(check[0].gross_total_minor??gross),
currencyCode:check[0].currency_code,
}),
input.userId??null,
],
);
for(const line of normalized) {
await tx.query(
`insert into invoice_lines(
id,invoice_id,line_number,description,quantity,unit_code,unit_price_net_minor,
net_amount_minor,tax_rate_basis_points,tax_amount_minor,gross_amount_minor,
source_entity_type,source_entity_id
) values($1,$2,$3,$4,$5::numeric,$6,$7,$8,$9,$10,$11,'check_item',$12)`,
[
randomUUID(),invoiceId,line.lineNumber,line.description,line.quantity,line.unitCode,
line.unitPriceNetMinor,line.netAmountMinor,line.taxRateBasisPoints,
line.taxAmountMinor,line.grossAmountMinor,line.sourceEntityId,
],
);
}
await tx.query(
`insert into invoice_tax_summaries(invoice_id,tax_rate_basis_points,taxable_net_minor,tax_minor,gross_minor)
select invoice_id,tax_rate_basis_points,sum(net_amount_minor),sum(tax_amount_minor),sum(gross_amount_minor)
from invoice_lines where invoice_id=$1 group by invoice_id,tax_rate_basis_points`,
[invoiceId],
);
await this.outbox.append({
organizationId:input.organizationId,
aggregateType:'Invoice',
aggregateId:invoiceId,
eventType:'InvoiceDraftCreated',
payload:{invoiceId,checkId:input.checkId},
},tx);
return {id:invoiceId,status:'draft'};
});
}
async createCreditNote(input:{
organizationId:string;locationId:string;originalInvoiceId:string;
reason:string;userId?:string;
}) {
assertCreditNoteInput(input);
return this.database.transaction(async(tx)=>{
const original=await tx.query<any>(
`select * from invoices
where id=$1 and organization_id=$2 and location_id=$3
and document_type='invoice' and status='finalized'
for update`,
[input.originalInvoiceId,input.organizationId,input.locationId],
);
if(!original[0]) throw new DomainConflictError('credit_note.original_invalid');
const existing=await tx.query<any>(
`select id from invoices
where original_invoice_id=$1 and document_type='credit_note'
and status in ('draft','validated','finalized')
limit 1`,
[input.originalInvoiceId],
);
if(existing[0]) throw new DomainConflictError('credit_note.already_exists');
const id=randomUUID();
await tx.query(
`insert into invoices(
id,organization_id,location_id,check_id,invoice_customer_id,document_type,status,
currency_code,gross_total_minor,net_total_minor,tax_total_minor,buyer_reference,
payment_reference,payment_terms_text,notes,source_snapshot_json,
original_invoice_id,created_by_user_id,e_invoice_status
)
select $1,organization_id,location_id,check_id,invoice_customer_id,'credit_note','draft',
currency_code,-gross_total_minor,-net_total_minor,-tax_total_minor,buyer_reference,
payment_reference,payment_terms_text,$2,
jsonb_build_object('originalInvoiceId',id,'originalInvoiceNumber',invoice_number),
id,$3,'not_requested'
from invoices where id=$4`,
[id,input.reason,input.userId??null,input.originalInvoiceId],
);
await tx.query(
`insert into invoice_lines(
id,invoice_id,line_number,description,quantity,unit_code,unit_price_net_minor,
net_amount_minor,tax_rate_basis_points,tax_amount_minor,gross_amount_minor,
accounting_revenue_account,source_entity_type,source_entity_id
)
select gen_random_uuid(),$1,line_number,description,-quantity,unit_code,-unit_price_net_minor,
-net_amount_minor,tax_rate_basis_points,-tax_amount_minor,-gross_amount_minor,
accounting_revenue_account,'invoice_line',id
from invoice_lines where invoice_id=$2
order by line_number`,
[id,input.originalInvoiceId],
);
await tx.query(
`insert into invoice_tax_summaries(invoice_id,tax_rate_basis_points,taxable_net_minor,tax_minor,gross_minor)
select $1,tax_rate_basis_points,-taxable_net_minor,-tax_minor,-gross_minor
from invoice_tax_summaries where invoice_id=$2`,
[id,input.originalInvoiceId],
);
await this.outbox.append({
organizationId:input.organizationId,
aggregateType:'Invoice',
aggregateId:id,
eventType:'CreditNoteDraftCreated',
payload:{creditNoteId:id,originalInvoiceId:input.originalInvoiceId},
},tx);
return {id,status:'draft'};
});
}
async finalize(input:{organizationId:string;locationId:string;invoiceId:string;userId?:string}) {
assertInvoiceReferenceInput(input);
return this.database.transaction(async(tx)=>{
const inv=await tx.query<any>(
`select i.*,ac.id as accounting_configuration_id,
ac.invoice_number_prefix,ac.credit_note_number_prefix,
ac.next_invoice_number,ac.next_credit_note_number
from invoices i
join accounting_configurations ac
on ac.organization_id=i.organization_id
and (ac.location_id=i.location_id or ac.location_id is null)
where i.id=$1 and i.organization_id=$2 and i.location_id=$3
and i.status in ('draft','validated')
order by case when ac.location_id=i.location_id then 0 else 1 end
limit 1
for update of i,ac`,
[input.invoiceId,input.organizationId,input.locationId],
);
if(!inv[0]) throw new DomainConflictError('invoice.finalize_invalid');
const row=inv[0];
const isCredit=row.document_type==='credit_note';
const seq=BigInt(isCredit?row.next_credit_note_number:row.next_invoice_number);
const prefix=isCredit?row.credit_note_number_prefix:row.invoice_number_prefix;
const number=`${prefix}-${String(seq).padStart(6,'0')}`;
await tx.query(
`update invoices
set invoice_number=$1,status='finalized',issue_date=current_date,
finalized_at=now(),version=version+1
where id=$2`,
[number,row.id],
);
await tx.query(
`update accounting_configurations
set next_invoice_number=case when $1 then next_invoice_number else next_invoice_number+1 end,
next_credit_note_number=case when $1 then next_credit_note_number+1 else next_credit_note_number end,
updated_at=now(),version=version+1
where id=$2`,
[isCredit,row.accounting_configuration_id],
);
await this.outbox.append({
organizationId:input.organizationId,
aggregateType:'Invoice',
aggregateId:row.id,
eventType:isCredit?'CreditNoteFinalized':'InvoiceFinalized',
payload:{invoiceId:row.id,invoiceNumber:number,originalInvoiceId:row.original_invoice_id??null},
},tx);
return {id:row.id,invoiceNumber:number,status:'finalized'};
});
}
async cancelDraft(input:{organizationId:string;locationId:string;invoiceId:string;userId?:string}) {
assertInvoiceReferenceInput(input);
const rows=await this.database.query<any>(
`update invoices
set status='cancelled',cancelled_at=now(),version=version+1
where id=$1 and organization_id=$2 and location_id=$3
and status in ('draft','validated')
returning id,status`,
[input.invoiceId,input.organizationId,input.locationId],
);
if(!rows[0]) throw new DomainConflictError('invoice.cancel_invalid');
return rows[0];
}
}

Architecture

High-level modules detected from the verified public snapshot. Protected implementation details are intentionally omitted.

appsmodule
.vscodemodule
.githubmodule

Technology stack

TypeScriptSQLMarkdownJSONGITIGNOREDOCKERIGNORENode.js / JavaScript ecosystem
TypeScript68.8%
SQL20.0%
Markdown10.9%
JSON0.3%
GITIGNORE0.1%
DOCKERIGNORE0.0%
CJS0.0%
MJS0.0%