PORTAL / LIBRARY / sales-doc

[ SALES & OPS ]

/sales-doc

Generate a sales offer, invoice, or contract for a ACME Agency prospect/client as a Google Doc, store it in the correct 2026 Drive folder (Ponude / Računi / Ugovori), and post the link to the #your-channel Slack channel.

Download the skill file (.md)

Placeholders like ACME Agency, <id> and you@example.com mark values that are per-agency — your install fills them with YOUR clients and accounts. If a section references a helper script you don't have yet, it ships with that workflow's install.

/sales-doc — offer, invoice, contract generator

Purpose

Sales closers at ACME Agency need to produce one of three Google Docs fast, right after a call:

  1. Ponuda (offer) — proposal with services + pricing
  2. Račun (invoice) — monthly billing with EUR total + BAM conversion
  3. Ugovor (contract) — business-cooperation contract ("Ugovor o poslovnoj saradnji")

Every doc is based on a tokenized master template on Drive. The skill copies the template, replaces {{PLACEHOLDERS}}, optionally deletes unused table rows, publishes the doc, and posts the link to #your-channel. The closer then opens the doc, fine-tunes anything specific, and downloads as PDF from the Docs UI.

When to trigger

Do not trigger for: general doc writing, ClickUp tasks, copywriting, or anything not in the three modes above.

Three modes

Mode A — Offer (--mode offer)

The offer renders from a single shell template (offer_shell) whose body is assembled at runtime from the module catalog in service_modules.json. The only fixed parts of every offer are the header, the pricing table, and the footer — everything in between is dynamically composed from the --modules list. No manual post-edits needed.

What you extract from the brief:

Productized tiers (--tier)

The four standard packages live in .claude/skills/sales-doc/tiers.json (single source of truth — edit numbers there). Each tier defines bounded deliverables (e.g. "do 4 vizuala mjesečno", "0 video oglasa") and a communication cadence (channel / calls per month / response SLA / reporting). This is what stops scope creep: the offer states a number, not "daily management".

--tierHR aliasesMonthlySetupMin budgetModules
lead_enginepokretač, engine690390500meta_ads, crm (1 channel)
growthrast1.0905901.000+ landing, email, tracking
scaleskaliranje1.7908902.000both channels + funnel
partnerdominacija3.9001.5004.000+ social media + newsletters (3 slots)

When --tier is passed (or auto-detected from price — see below), the offer renders a {{PACKAGE_BLOCK}} section (OPSEG PAKETA + KOMUNIKACIJA + DODATNE USLUGE add-on price list) before the price table, and the contract renders Član 4 — OPSEG USLUGA I DODATNE USLUGE ({{PACKAGE_ANNEX}}: bounded scope + comms cadence + out-of-scope clause + add-on cjenovnik) right after the price article. Both fill from tiers.json. The offer block is empty on non-tier offers; the contract's Član 4 falls back to a "scope is defined in the offer" sentence so the article is never empty. The à-la-carte add-on list also lives in tiers.json for the "recharge when they ask for too much" mechanism.

Template placeholders are inserted by node ACME Agency/scripts/<id>.mjs (idempotent; --revert removes them). The contract's scope was promoted from an end-of-doc annex to a numbered Član 4 (articles renumbered Č4→Č11) by node ACME Agency/scripts/<id>.mjs (one-time, idempotent). Re-run either only if a template is recreated.

Auto-detect + pricing flags:

Full reasoning + closer talk-track + the onboarding call SOP + the pitch brochure (HTML→PDF) live in ACME Agency/strategy/ and the internal Drive folder "ACME Agency — Agency Self / Paketi i Pozicioniranje 2026". Regenerate the strategy/one-pager/SOP docs with node ACME Agency/strategy/build_package_docs.mjs and the brochure with node ACME Agency/strategy/build_pitch_brochure.mjs.

Module catalog (service_modules.json):

Module idOffer section (numbered in doc)
meta_adsPerformance marketing — Meta bullets
google_adsPerformance marketing — Google bullets
landingIzrada prodajne stranice
crmIntegracija sa CRM platformom i buking kalendarom (full)
emailEmail automatizacija (sales-pipeline triggers)
trackingTracking setup — bullet flavor picked by which ad platforms are in modules
cold_email_accountsKreiranje novih email računa — params: inbox_count (default 30)
cold_email_listGenerisanje email liste — params: prospects_count (default 2.000)
cold_email_campaignKreiranje cold email kampanje
cold_crmCRM za praćenje upita (short cold-outbound version)
linkedinKreiranje Cold LinkedIn kampanje — becomes a bullet section, NOT a line-item price

Legacy aliases: metameta_ads, googlegoogle_ads (accepted for backwards-compat).

Section ordering:

Mapping natural-language briefs → flags:

Closer phraseModules
"Meta + Google, full stack"meta_ads,google_ads,landing,crm,email,tracking
"Meta only"meta_ads,landing,crm,email,tracking
"Google only"google_ads,landing,crm,email,tracking
"Just ads, no landing page"meta_ads,google_ads,tracking
"Cold outbound + LinkedIn"cold_email_accounts,cold_email_list,cold_email_campaign,cold_crm,linkedin
"LinkedIn outbound only"linkedin,cold_crm

If the closer doesn't specify --service-tag, derive from modules:

Mode B — Invoice (--mode invoice)

What you extract:

First-invoice rule: if the closer says "first invoice", "prvi račun", "onboarding invoice", or this is clearly the first billing cycle for a new client (same month as the offer signing) AND the offer had a setup fee, include a Setup fee line in --items with the same amount as the offer's setup. Example — closer says "first invoice for Cekić, 1100 monthly" after an offer with 1100 setup → --items "Mjesečne usluge:1100,Setup fee:1100". Subsequent monthly invoices omit setup.

Legal details (OIB, address) auto-resolve for Croatian clients. Skill fails fast with clear list of missing fields if they can't be resolved — closer either adds them to ACME Agency/clients/legal.json or passes override flags:

Examples:

Closer phraseFlags
"Invoice for ACME Agency, 1250 mjesečno, BF 4511"--client "ACME Agency" --items "Mjesečne usluge:1250" --bf 4511
"Račun za ACME Agency, 1000 mjesečno, budžet Njemačka 1750, budžet HR+SLO 300, BF 4933"--client "ACME Agency" --items "Mjesečne usluge:1000,Budžet Njemačka:1750,Budžet Hrvatska + Slovenija:300" --bf 4933
"Invoice Perfect Solution, 550 mjesečno + 550 setup + 600 budžet, BF 5349"--client "Perfect Solution" --items "Mjesečne usluge:550,Setup fee:550,Budžet:600" --bf 5349

Mode C — Contract (--mode contract)

What you extract:

If legal info is missing after auto-resolution, skill prints a clear message listing which fields are unresolved and exits — never writes a half-filled contract.

How the skill runs

  1. Parse flags (or natural-language brief → flags via Claude).
  2. Load config.json; fail if template ID for the mode is null (setup script needs to run first).
  3. Look up client legal details (invoice + contract modes only).
  4. Compute derived values: total EUR, BAM conversion, invoice number, assembled services block.
  5. Copy template → destination folder with a dated title.
  6. replaceAllText for every token.
  7. Delete empty table rows via {{__EMPTY__}} sentinel (invoice items, offer extra line).
  8. makePublic + get URL.
  9. Post one message to #your-channel with the link.

Setup

Before first use, run the template setup sequence (order matters — shell depends on offer_standard):

node ACME Agency/scripts/<id>.mjs
node ACME Agency/scripts/setup_sales_doc_shell.mjs

The first script creates a Sales templates/ subfolder in Klijenti on Drive and produces invoice + contract + original offer templates with {{TOKENS}}. The second script derives a single "shell" offer template from the standard offer (hardcoded services block collapsed into {{SERVICES_BLOCK}}, header service label to {{SERVICE_TAG}}, budget note to {{BUDGET_NOTE_BLOCK}}). Template IDs are written to .claude/skills/sales-doc/config.json.

Invocation examples

node .claude/skills/sales-doc/script.mjs --mode offer \
  --client "AGS Group d.o.o." \
  --modules meta_ads,google_ads,landing,crm,email,tracking \
  --monthly 1000 --setup 1000 --budget-min 700

# Cold outbound + LinkedIn example (DBCI 2026-04-24)
node .claude/skills/sales-doc/script.mjs --mode offer \
  --client "D-Business Consulting International d.o.o." \
  --modules cold_email_accounts,cold_email_list,cold_email_campaign,cold_crm,linkedin \
  --module-params "cold_email_accounts.inbox_count=30,cold_email_list.prospects_count=2.500" \
  --monthly 1600 --setup 1600

node .claude/skills/sales-doc/script.mjs --mode invoice \
  --client "ACME Agency d.o.o." \
  --items "Mjesečne usluge:1000,Budžet Njemačka:1750,Budžet Hrvatska + Slovenija:300" \
  --bf 4933

node .claude/skills/sales-doc/script.mjs --mode contract \
  --client "NIKO d.o.o." --monthly 690 --setup 890 \
  --services "Google Ads i landing page" --rep-name "Goran Leutar" --rep-role direktor

Output

Single Slack message in #your-channel:

*Nova ponuda — AGS Group d.o.o.*
Performance marketing | 1.000,00 EUR/mj + 1.000,00 EUR setup | budžet od 700 EUR/mj

Doc: https://docs.google.com/document/d/.../edit
Folder: Ponude 2026

Closer opens the Doc, fine-tunes, downloads as PDF, sends to prospect.

Branded PDF offer (the Paradox-styled deliverable)

The Doc above is the editable offer. For a send-ready, designed PDF in the Paradox house style (dark cinematic + lime accent, Clash Display / General Sans, the your-domain.example wordmark — same look as the pitch brochure), use the branded-PDF engine. This is the standard polished offering a closer hands a prospect.

Trigger this path when the closer says any of: "ponuda u našem stilu / u našem PDF-u / u Paradox stilu", "nice PDF", "branded offer", "make it look like the ACME Agency one", or just "PDF" alongside an offer brief. It is also the right default whenever the offer is going straight to the client rather than needing manual fine-tuning. When in doubt, produce both — the editable Doc (normal flow) and the branded PDF.

Engine: ACME Agency/scripts/build_offer_pdf.mjs (renderer: scripts/lib/offer_pdf.mjs). It is deterministic: renders the PDF, self-verifies it exists and is a real file (validateFiles, ≥ 8 KB), and uploads to the client's Drive folder.

Flow (what the skill does):

  1. Parse the closer's brief into a spec object (below). Reuse the SAME module ids as the Doc offer (google_ads, linkedin, landing, crm, email, tracking, …) so the PDF breakdown matches the Doc wording verbatim — the engine expands them from service_modules.json. Use explicit services[] only for bespoke bullets not in the catalog.
  2. Write the spec to a JSON file (e.g. ACME Agency/clients/ACME Agency/examples/<slug>.json — see ACME Agency-skaliranje.json for a full worked example).
  3. Run: node ACME Agency/scripts/build_offer_pdf.mjs --spec <path.json> (folder resolves from spec.drive_folder_id, or pass --client "<name>" to look it up in clients.json, or --folder <id>). Add --no-upload to pACME Agencyw locally first.
  4. Return the Drive link. Croatian copy rules apply (no em-dashes, native phrasing, diacritics intact).

Spec contract (all fields optional except client + pricing.monthly + one of services/modules):

FieldMeaning
client"ACME Agency Group d.o.o." — header + footer + Drive lookup
date"29.06.2026." (Croatian format, trailing dot)
eyebrowsmall label above the title (e.g. "Nastavak saradnje"); default "Ponuda"
title / title_accentcover headline; title_accent renders in lime on its own line
intro2-3 sentence cover paragraph (HTML <b> allowed)
modulesarray of catalog ids → auto-expanded to service cards
servicesarray of { name, bullets:[], badge? } for custom blocks (appended after modules)
pricing{ monthly, was?, setup?, setup_waived?, currency? }was shows the old price struck; setup_waived:true → "0 €" + "bez setup naknade"; currency EUR/BAM
notesarray of value-add / incentive / budget lines (the bordered boxes on the ponuda page; HTML <b> allowed)
closingclosing paragraph above the signature
signer{ name, role } (default Faris Biogradlić / Head of Growth, ACME Agency)
filenameupload filename (no extension)
drive_folder_idtarget Drive folder (else --client / --folder)

What the closer gives you → what he sold (→ modules/services), the price (→ pricing.monthly), the incentive (a discount → pricing.was + a notes line; free add-on → a badge on that service + a notes line; waived setup → setup_waived:true), and optionally a date (→ date). Everything else has sane defaults.

Example (a teammate's brief → spec → PDF):

"napravi ponudu za ACME Agency u našem stilu: google ads, smm, linkedin ads (gratis), optimizacija profila, 3 landinga, crm+tracking — 2000€/mj, bez setupa, prošla cijena 3000€"

→ build the spec (modules + the two LinkedIn blocks as services with a "Uključeno kao dodatna vrijednost" badge, pricing:{monthly:2000, was:3000, setup_waived:true}, a notes line for the free LinkedIn value) → node ACME Agency/scripts/build_offer_pdf.mjs --spec ACME Agency.json → branded PDF in ACME Agency's Drive folder. See [clients/ACME Agency/examples/ACME Agency-skaliranje.json](../../../ACME Agency/clients/ACME Agency/examples/ACME Agency-skaliranje.json).