Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, IT partner for business & fractional CTO. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

[email protected]
Back to projects

Iurii Manages: pi-pi.ee — B2B Deal & Document Portal

July 1, 2026

Internal sales portal that turns a wholesale deal into a full set of trade paperwork — pro forma, contract, commercial invoice, packing list, CMR and more — from a single form, so a rep can quote and close international orders without a lawyer or a customs broker on every deal.

Stack

Next.jsReactTypeScript

Services

Notion

Topics

B2BCRMSales AutomationPDFSSRArchitectureInternational TradeInternal Tools

Key Results

  • Close international orders faster — an afternoon of paperwork per deal becomes one form and a set of downloads
  • Fewer stalled shipments — 7 trade documents generate from one record, so prices and VAT numbers never disagree at the border
  • Quote and close routine deals without pulling in a lawyer or customs broker
  • Correct pricing every time — wholesale tiers, VAT and reverse-charge applied automatically per buyer country
  • No new software cost — runs on the Notion CRM the team already uses, no per-seat licence
pi-pi.ee — B2B Deal & Document Portal

The Business Problem

Pi-Pi manufactures waterless urinals and sells them wholesale to businesses across Europe — distributors, facility managers, venue operators. The product is easy to sell. The paperwork behind each sale is not.

Every international B2B order drags a stack of documents with it: a pro forma invoice to lock the offer, a technical specification, a supply contract, a commercial invoice for customs, a declaration of conformity, a packing list, and a CMR consignment note for the truck. Seven documents, each carrying the same buyer details, the same prices, the same quantities — and each one previously assembled by hand in Word and Excel.

That manual process cost the business in three ways. It was slow: a rep spent an afternoon per deal copying figures between templates. It was error-prone: one mistyped VAT number, or a price that didn't match between the invoice and the contract, could stall an order at the border or trigger an awkward call with the buyer. And it didn't scale: closing more deals meant more afternoons lost to formatting, not more selling.

The goal was to make the paperwork stop being a job — the rep fills in what actually varies about a deal, and every document generates itself, guaranteed consistent.

The Solution

I built an internal portal where the whole deal lives in one record. It works like a lightweight CRM — each client holds all of their deals, and each deal remembers the exact buyer details it was issued with — and every trade document derives from that single source of truth. The stack is deliberately lean; the engineering is in the business rules, not the infrastructure.

One deal, seven documents. The pro forma, specification, contract, commercial invoice, conformity declaration, packing list and CMR note are all generated from the same deal record, so a change to a price or a VAT number propagates to all of them at once. This is the guarantee that keeps orders moving through customs: the numbers on the invoice, the contract and the packing list can never disagree, because they read from one place. The rep sees the documents grouped by the stage of the deal — offer, order confirmation, shipment — and downloads any of them as a finished PDF, with Cyrillic support for Eastern-European buyers.

Pricing, VAT and payment terms handled automatically. The commercial rules the rep used to look up by hand are encoded once. VAT is applied per buyer country, the EU cross-border B2B reverse-charge case drops the rate to zero, and the payment-terms wording on every document is generated from the deposit percentage rather than free-typed — so 50% always produces the same correct sentence everywhere. Wholesale quantity-break discount tiers live in one table, so the buyer gets the right price for their volume without the rep pricing it manually.

Shipping figures worked out for you. A freight forwarder and a customs form both need the shipment broken into pallets with weights and volumes — figures the rep used to work out on a calculator. From the order quantities, the portal groups units by product and splits each group into the fewest, evenly balanced pallets. Gross weight and volume are derived automatically and flow straight onto the packing list and CMR note.

No new software to run. There is no separate database to maintain or back up: the deal data lives in the Notion CRM the team already uses. Each buyer's details are snapshotted at deal creation, so a document reissued months later still matches what the customer signed — even if the client record was edited since. No migration, no per-seat licence, no second place to keep customer records in sync.

Results

What used to be an afternoon of copy-paste per order is now a single form and a set of downloads. The rep quotes faster, the documents always agree with each other, and routine international orders go out without pulling in a lawyer or a customs broker to check the paperwork. The technical build is modest by design — its value is entirely in the business it unblocks.

MetricValue
Documents per deal7 (pro forma, spec, contract, commercial invoice, conformity, packing list, CMR)
Source of truth1 deal record — every document, price and weight derives from it
Pricing & VATAutomatic — wholesale tiers + reverse-charge by buyer country
Shipping figuresPallets, weight and volume computed from order quantities
DatastoreNotion — no separate database, no new licence
RenderingServer-side PDFs (@react-pdf/renderer), Cyrillic-capable

Under the Hood

For the technically curious, here is how the core pieces are built. The stack is Next.js App Router with server-rendered PDFs (@react-pdf/renderer) and Notion for storage.

One Deal, Seven Documents

Every document is a pure function of the same Deal object, so a change to a price or a VAT number propagates to all of them at once.

// src/lib/pdf/documents/generate.ts
export async function generateDealDocument(deal: Deal, docType: DocType): Promise<Buffer> {
  let element;
  switch (docType) {
    case "proforma":
      element = ProformaInvoicePDF({ deal });
      break;
    case "specification":
      element = SpecificationPDF({ deal });
      break;
    case "commercial-invoice":
      element = CommercialInvoicePDF({ deal });
      break;
    case "contract":
      element = SupplyContractPDF({ deal });
      break;
    case "packing-list":
      element = PackingListPDF({ deal });
      break;
    case "cmr":
      element = CmrNotePDF({ deal });
      break;
    case "conformity":
      element = ConformityPDF({ deal });
      break;
  }
  return Buffer.from(await renderToBuffer(element)); // @react-pdf/renderer, server-side
}

VAT, Reverse Charge and Payment Terms as Deal Logic

VAT rates are static per-country data (no runtime API call in the critical path), the EU cross-border B2B reverse-charge case flips the rate to zero, and the payment-terms sentence on every document is generated from the deposit percentage rather than free-typed.

// src/lib/admin/deal-defaults.ts
// VAT applies to goods + freight; reverse-charge deals carry vatRate = 0.
export function dealVatAmount(deal: Deal): number {
  return (dealSubtotal(deal) + dealShipping(deal)) * deal.vatRate;
}
 
// Payment terms follow the deposit — no free-text field to get wrong.
export function dealPaymentTerms(deal: Deal): string {
  const dep = deal.depositPercent;
  if (dep >= 100) return `Full prepayment (100%) payable within 7 days...`;
  if (dep <= 0) return `Payment in full within 7 days of order confirmation...`;
  return `${dep}% deposit within 7 days; remaining ${100 - dep}% before dispatch...`;
}

Automatic Pallet Planning

From the order quantities, the portal groups units by SKU (pallet capacity is box-size specific, so SKUs never share a pallet) and splits each group into the fewest, evenly balanced pallets — 100 units become 50 + 50, never 64 + 36.

// src/lib/admin/deal-defaults.ts
// (100, 64) → [50, 50];  (130, 64) → [44, 43, 43];  (46, 45) → [23, 23]
export function balancedLoads(totalUnits: number, max: number): number[] {
  if (totalUnits <= 0) return [];
  const count = Math.ceil(totalUnits / max);
  const base = Math.floor(totalUnits / count);
  const remainder = totalUnits - base * count;
  return Array.from({ length: count }, (_, i) => base + (i < remainder ? 1 : 0));
}

Gross weight and volume are then derived from catalogue mass plus carton and pallet tare, and flow straight onto the packing list and CMR note.

Notion as the Datastore

Each client is a Notion page and each deal is persisted as a single JSON blob. The buyer block is snapshotted from the client at deal creation, so a document reissued months later still matches what the customer signed.

// src/lib/admin/deal-defaults.ts
// A new deal copies the client's party fields into its own buyer snapshot.
export function emptyDealForClient(client: DealClient): Deal {
  const deal = emptyDeal();
  deal.clientId = client.notionPageId;
  deal.buyer = {
    company: client.company,
    email: client.email,
    vatNumber: client.vatNumber /* ... */,
  };
  return deal;
}
Iurii RoguliaAvailable

Need something similar?

I build custom solutions — from APIs to full products. Let's talk about your project.

View all projects

Related projects

pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems
January 17, 2026
pi-pi.ee — B2B E-commerce for Waterless Urinal Systems

i18n B2B e-commerce platform for waterless urinal products across 32 European countries with automated VAT handling, PDF invoicing, and CRM integration.

Stack

Next.jsReactTypeScriptTailwind CSS

Libraries

next-intlDaisyUIStripe SDKreact-pdf

Services

StripeVercelResendNotionGoogle Analytics

Topics

E-commerceB2Bi18nPDFSSRArchitectureSEOSchema.org
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
Pikkuna — E-commerce for Vinyl Curtains & PVC Products
October 12, 2024
Pikkuna — E-commerce for Vinyl Curtains & PVC Products

International e-commerce platform with 30 locales, product configurators, AI chatbot, and fully automated order flow: Stripe → Zoho CRM → Airtable → Mailgun →

Stack

Next.jsReactTypeScript

Libraries

next-intlZodpdf-libPuppeteerStripe.js

Databases

Redis

Services

StripeZohoMailgunPostNordNetvisorVercelVercel BlobGoogle AnalyticsMeta CAPIAirtableUpstash

Topics

E-commercePaymentsShippingPDFPWACTOArchitectureSEOSchema.orgi18nPerformance

Related posts

B2B Quote-to-Order Flow in Next.js: A State Machine That Doesn't Drift
August 7, 2026· 10 min
B2B Quote-to-Order Flow in Next.js: A State Machine That Doesn't Drift

B2B quote-to-order flow in Next.js: an RFQ → quote → order state machine, per-deal line items, guarded status transitions, and converting an accepted quote

Stack

Next.jsReactTypeScript

Databases

PostgreSQL

Topics

B2BE-commerceArchitectureSSRSales Automation
Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access
July 24, 2026· 9 min
Gated B2B Pricing in Next.js: Hiding Prices Behind Per-Customer Access

Gated B2B pricing in Next.js: per-account price lists, server-side access control so prices never leak to crawlers, and a deal-record data model where every

Stack

Next.jsReactTypeScript

Databases

PostgreSQL

Topics

B2BE-commerceAuthSSRArchitectureSecurity