Full Design Quotation System
This skill renders bill of quantities data into professional PDF quotations, supporting multi-page output with cover, total price table, and detailed pages.
1. When to Use This Skill
Trigger Conditions
Use this skill in the following scenarios:
- User enters
/quote- Generate PDF quotation - User mentions converting quotation data from a multidimensional table or Excel into a PDF quotation
Do not use this skill in the following scenarios:
- User is simply editing multidimensional table data (use lark-base skill instead)
- User is just viewing quotation history (no regeneration needed)
Prerequisites
lark-baseskill - Read Feishu multidimensional table datalark-imskill - Send PDF to Feishu chatlark-driveskill - Upload PDF to Feishu Docsxlsxskill - Parse Excel files (when user provides Excel)- Node.js runtime + Playwright (already installed in the project directory)
Step 0: First-Time Use Check
When the user uses the Skill for the first time or encounters dependency-related errors when executing commands, the Agent should perform the following check process.
1. Check Node.js Environment
Try executing node --version, require >= 18. If it fails or the version is too low:
- Prompt the user to install Node.js (>= 18): https://nodejs.org
- Suggest using nvm to manage versions
2. Check Playwright Browser
Check if npx playwright install chromium has been completed in the project directory. If render.js throws an Executable doesn't exist error:
- Run in the project directory:
npx playwright install chromium - Retry rendering
3. Check lark-cli
Try executing lark-cli --version. If it fails (command not found):
- Prompt the user to install lark-cli (Feishu command-line tool)
- After installation, run:
lark-cli auth login
4. Check Feishu Login Status
Try executing lark-cli base +table-list --base-token LfjJbLrTHacijesHmIjcDtSpnJf to test login status. If an authentication error is returned:
- Prompt the user to run:
lark-cli auth login - Retry after completion
5. Provide Feishu Template
The user needs to copy the project template to their own Feishu space:
- Template link: https://li1fn1sw90.feishu.cn/base/LfjJbLrTHacijesHmIjcDtSpnJf?from=from_copylink
- Guide the user to click the link → Click "Copy this multidimensional table" → Fill in project data and quotation details in their own space
- After completion, send the new multidimensional table link to the Agent
Excel Data Source Processing
When the user provides an Excel file, do not directly parse and render. Guide the process:
- Prompt: Recommend first importing to Feishu multidimensional table for better field and order alignment
- Guide the user to import Excel into Feishu:
- Open Feishu → New multidimensional table → Import → Select Excel file
- Or directly copy the template multidimensional table and paste Excel data into it
- After the user provides the multidimensional table link, continue the
/quoteprocess
2. Workflow
User triggers → Environment check → Confirm data source → Read data → Data validation → Confirm logo → Confirm tax rate → Confirm template → Render PDF → Send to Feishu
Step 1: Confirm Data Source
The user must provide one of the following:
- Feishu multidimensional table link - e.g.,
https://xxx.feishu.cn/base/XXX - Excel file - Local file path or uploaded via chat
If the user has not provided one, ask the user to choose a data source.
Step 2: Read Data
Feishu Multidimensional Table Data Source
- Extract the
base-tokenfrom the user-provided link (the part after/base/in the URL) - List tables:
lark-cli base +table-list --base-token <token> - Find the table IDs for Project Information and Quotation Details
- Read project information:
lark-cli base +record-list --base-token <token> --table-id <Project Information table_id> --format json - First, query views:
lark-cli base +view-list --base-token <token> --table-id <Quotation Details table_id>, get the view_id of the default grid view (e.g., "All Items") - Read quotation details (must include
--view-id, otherwise the return order will not match the user's drag-and-drop order in the Feishu interface):lark-cli base +record-list --base-token <token> --table-id <Quotation Details table_id> --view-id <View ID> --format json --limit 200 - Extract from project information: Project Name, Engineering Number, Preparation Date, Prepared By, Contact Email, Company Logo, Tax Rate, Management Fee
- Extract from quotation details: Area, Engineering Category, Item Name, Item Description, Unit, Quantity, Unit Price, Total Price, Remarks (Serial number does not need to be read; it is automatically generated during rendering)
Excel Data Source
Use the xlsx skill to parse the Excel file and extract data with the same structure. Excel may have multiple sheets; find the sheet containing quotation details (usually with column headers like "Serial No.", "Engineering Category", "Unit Price", etc.).
Step 3: Data Validation
Before rendering the PDF, the Agent must validate the read data. Do not skip directly.
Validation Checklist (execute item by item):
3.1 Project Information Completeness
Check the three fields: Project Name, Engineering Number, Preparation Date:
- Any is empty → Inform the user which specific field is missing and ask them to fill it in the "Project Information" table in Feishu
- All pass → Continue
3.2 Number of Quotation Detail Items
Check the items array:
- Length is 0 or does not exist → "There is no data in the quotation details. Please add items in the 'Quotation Details' table in Feishu and try again."
- Has data → Continue
3.3 Item-by-Item Field Check
Check required fields (Engineering Category, Item Name, Unit, Quantity, Unit Price) for each record, and generate a summary report:
Data Validation Results:
- Total items: 104
- Missing Area: 3 (Items 6, 9, 20)
- Missing Engineering Category: 2 (Items 5, 18)
- Missing Item Name: 0
- Missing Unit: 1 (Item 12)
- Missing Quantity: 0
- Missing Unit Price: 3 (Items 5, 7, 22)
- Engineering Category not in standard list: 0
3.4 Handling Missing Fields
- Missing items ≤ 20% of total items: Inform the user which specific items have issues and ask "Do you want to skip these items and continue generation?"
- Missing items > 20%: Ask the user to correct the data and retry; do not continue rendering
- User confirms to skip → Filter out the problematic items and continue with valid data
3.7 Missing Area Check (only when grouping by area)
When the user selects to group by area (Step 5.6 selects "By Area"), additionally check the Area field for each record:
Missing Area: X (Item X)
- Missing items ≤ 20% of total items: Inform the user which specific items lack an area and ask "Do you want to categorize these items under 'Other' and continue generation?"
- Missing items > 20%: Ask the user to fill in the areas and retry
- User confirms → Missing area items are categorized under "Other" (mutually exclusive with Step 3.4 skip logic; user chooses one: skip or categorize as Other)
3.5 Automatic Total Price Correction
For each record, check if Total Price equals Quantity × Unit Price:
- Deviation < 1 yuan → No action
- Deviation ≥ 1 yuan or empty → Automatically calculate
Total Price = Quantity × Unit Price, write the value into rendering data - Corrected items exceed 10% → Prompt the user "X items' total price have been automatically corrected," suggest checking the multidimensional table formulas
3.6 Standard Engineering Category List
The following are the 13 standard categories:
Measure Project, Demolition Works, Masonry Works, Concrete & Reinforced Concrete Works,
Metal Structure Works, Waterproofing Works, Thermal Insulation Works, Floor & Ground Finishing Works,
Wall & Column Finishing & Partition Works, Ceiling Works, Paint & Coating Works,
Other Decorative Works, Installation Works
Categories not in the list can still be rendered, but inform the user: "Category 'XXX' is not in the standard list."
Step 4: Logo Processing
Must be executed, cannot be skipped. Regardless of whether the data source is a multidimensional table or Excel, the logo must be checked and processed.
- Read from Multidimensional Table: Check the
Company Logoattachment field in the Project Information table- If there is an attachment, download using the media API (
drive +downloaddoes not support multidimensional table attachments):lark-cli api GET /open-apis/drive/v1/medias/{file_token}/download --output ./logo.png - Convert the image to a base64 data URI and pass it to the
logo_urlfield in the rendering data
- If there is an attachment, download using the media API (
- Read from Excel: Check if the Excel file contains a logo image (usually embedded in a sheet or as an attachment)
- If yes, extract and convert to data URI
- When Logo is Missing (must ask the user):
- If no logo image is found in the multidimensional table or Excel, must ask the user:
- "No company logo found in the data source. How would you like to proceed?"
- Option A: Upload a logo image
- Option B: Provide a logo image URL
- Option C: Do not use a logo (display a default "R M" text identifier in the PDF)
- Do not skip this step by default. Even if the user says "just generate," confirm the logo handling method.
- If no logo image is found in the multidimensional table or Excel, must ask the user:
Step 5: Confirm Tax Rate and Management Fee
Prioritize using the Tax Rate and Management Fee field values in the multidimensional table's project information. If these fields do not exist in the multidimensional table, ask the user.
Rate Rules (common for Tax Rate and Management Fee):
- Value > 1 → Treated as a percentage integer, automatically ÷100 (e.g.,
3→ 3%,10→ 10%) - Value ≤ 1 → Treated as a decimal and used directly (e.g.,
0.03→ 3%,0.1→ 10%) - Empty value: Tax rate defaults to 3%, Management fee defaults to 0 (not charged)
Calculation Order:
Subtotal = Σ Total Price of each item
Management Fee = Subtotal × Management Fee Rate
VAT = (Subtotal + Management Fee) × Tax Rate
Grand Total = Subtotal + Management Fee + VAT
When Management Fee > 0, the total price table shows the "Management Fee" row; when Management Fee = 0, it is not displayed (compatible with old data).
Step 5.5: Confirm Template Style
Must ask, cannot be skipped. Even if a template was used before, confirm it every time.
Ask the user to choose a template:
Please select the quotation template style:
1. Swiss IKB (Default) - Blue full-bleed cover + bilingual category titles
2. Swiss IKB Zebra - Same as above + zebra striping on detail rows (white/light blue alternating)
3. B&W - White cover + light gray accents, suitable for black & white printing
4. B&W Zebra - Same as above + zebra striping on detail rows (white/light gray alternating)
If the user's reply includes a clear template name or number, use the corresponding template:
swiss-ikb- Swiss IKB (Default)swiss-ikb-zebra- Swiss IKB Zebrabw- B&W Black & White Print Versionbw-zebra- B&W Zebra Black & White Print Zebra Version
Under Development (not yet publicly available): The code for the Editorial / Editorial B&W / Card / Card B&W templates already exists, pending user modification and improvement before activation. If a user actively requests these templates, commands like
node scripts/render.js --template editorialcan be executed, but priority should be given to recommending the 4 live templates above.
Step 5.6: Confirm Grouping Method
Must ask, cannot be skipped. Confirm before each render; do not default to the last option.
Ask the user to choose the grouping method:
Please select the quotation grouping method:
1. By Area (Foyer, Living Room, Master Bedroom…) - Summary table grouped by area, detail table adds an Engineering Category column after the serial number
2. By Engineering Category (Measure Project, Demolition Works…) - Traditional method, detail table does not include an Engineering Category column
After the user replies with a clear number or name, use the corresponding mode:
area- Group by area, add--group-by areato the render commandcategory- Group by engineering category (default), do not pass or add--group-by category
Note: When selecting "By Area," ensure the
Areafield in the quotation detail table is filled in (checked in Step 3.7). If the data does not have area information yet, it is recommended to use "By Engineering Category" first.
Step 5.7: Area Name English Translation (only when grouping by area)
Area names vary by project. Must be dynamically translated before each render; hardcoded mappings are strictly prohibited.
- Collect all area names appearing in this quotation (deduplicated)
- The Agent translates each area name into English (conventional design industry translations, e.g., 玄关→Foyer, 客厅→Living Room, 阳光房→Sunroom)
- Write the translation results as a
region_namesobject into the rendering data JSON:
{
"region_names": {
"玄关": "Foyer",
"阳光房": "Sunroom"
}
}
- During rendering,
render.jsprioritizes the translations inregion_names(unprovided area names fall back to a built-in common area mapping, and finally display AREA) - After rendering, list the area translation mapping in the report. The user can correct it; translations will be redone for the next quotation.
Step 6: Render PDF
Execute in the project directory:
node scripts/render.js --input <data.json> --template <Template Name> --vat-rate <Tax Rate> --group-by <area|category> --output ./output/<Project Name>_<Engineering Number>.pdf
--group-by area- Group by area (major category = area, details include Engineering Category column)--group-by categoryor not passed - Group by engineering category (original behavior)
Rendering data JSON format:
{
"项目名称": "xxx",
"工程编号": "xxx",
"编制日期": "xxx",
"编制人员": "xxx",
"联系邮箱": "xxx",
"logo_url": "data:image/png;base64,...",
"税率": 0.08,
"管理费": 0.1,
"items": [
{
"区域": "玄关",
"工程分类": "措施项目",
"项目名称": "脚手架",
"项目特征": "室内脚手架",
"单位": "项",
"数量": 1,
"综合单价": 3200,
"合价": 3200
}
]
}
Automatic Serial Number Generation Rules: The
序号field does not need to be provided in the data (the column can be omitted in the Feishu multidimensional table). During rendering, it is automatically generated usingGroup Serial Number.Item Serial Number within Group: When grouping by area, Area 01 →1.1, 1.2, …, Area 02 →2.1, 2.2, …; When grouping by engineering category, Measure Project →1.x, Demolition Works →2.x… Installation Works →13.x. Item order within a group = Feishu record order (records can be sorted by dragging in the multidimensional table).
Tax Rate & Management Fee (see Step 5 for details):
- Values for
税率/管理费> 1 are treated as percentage integers (3 → 3%), ≤ 1 are treated as decimals (0.08 → 8%) - Management Fee = Subtotal × Management Fee Rate; VAT = (Subtotal + Management Fee) × Tax Rate; Grand Total = Subtotal + Management Fee + VAT
- Management Fee not provided or 0 → not charged, Management Fee row not shown in total price table
- If the
--vat-rateparameter is not provided,render.jsfirst reads the税率field from the data
Step 7: Save and Send
After PDF rendering is complete, must ask the user to choose a save method:
PDF generated: <Project Name>_<Engineering Number>_<Template Name>.pdf (XX items, ¥XXX including tax)
How would you like to save it?
1. Send to current chat window
2. Save to Feishu Cloud Docs, push document link
3. Both
Execute based on the user's choice:
Option 1 - Send to Chat Window
lark-cli im +messages-send --type media --file <PDF Path>
Option 2 - Save to Feishu Cloud Docs
lark-cli drive +upload --file <PDF Path> --title "<Project Name>_<Engineering Number>"
Get the uploaded file link and reply to the user:
Saved to Feishu Cloud Docs: <Project Name>_<Engineering Number>_<Template Name>.pdf
Document Link: https://xxx.feishu.cn/drive/xxx
Option 3 - Both
First execute Option 2 (upload), then execute Option 1 (send file + link).
Clean up temporary files
After sending/saving is complete, delete temporary JSON files and logo download files (if any) generated during the rendering process.
3. Project Structure
quote-generator-skill/
├── package.json # Project configuration
├── setup.sh # One-click installation script
├── SKILL.md # Skill definition
├── scripts/
│ ├── render.js # HTML → PDF rendering engine
│ ├── demo.js # Out-of-the-box demo
│ └── generate-large-test.js # Large test data generator
├── references/
│ ├── templates/
│ │ ├── content.html # Handlebars PDF template (content pages)
│ │ ├── cover-screen.html # Swiss IKB cover standalone template
│ │ ├── cover-bw.html # B&W Black & White print cover template
│ │ ├── swiss-ikb.json # Swiss IKB configuration
│ │ ├── swiss-ikb-zebra.json # Swiss IKB Zebra configuration
│ │ ├── bw.json # B&W Black & White print configuration
│ │ └── bw-zebra.json # B&W Zebra configuration
│ ├── helpers.js # Handlebars custom helpers
│ └── bitable-config.json # Multidimensional table field configuration
├── docs/plans/ # Design documents
├── output/ # Generated PDF output directory
└── README.md
4. Template Description
PDF templates contain three types of pages:
- Cover Page - Project name, number, date, prepared by, logo (top right)
- Summary Table - Amounts summarized by engineering category + Subtotal/VAT/Grand Total
- Detail Pages - Approximately 8 rows per page, including category titles, detail rows, subtotal rows, and page numbers
5. Notes
- The
Total Pricein the multidimensional table is a formula field (=Quantity × Unit Price) and is already calculated when read Serial Numberdoes not need to be maintained in Feishu: automatically generated during rendering (Group Serial Number.Item Serial Number within Group, item order within group follows record order). To adjust the detail order, drag and drop records in the Feishu multidimensional tableEngineering Categoryis a single-select field, with 13 options corresponding to 13 engineering categoriesAreais a free text field (it is recommended to change it to single-select in Feishu: Foyer, Living Room, Dining Room, Kitchen, Master Bedroom, Secondary Bedroom, Bathroom, Balcony, Study, Walk-in Closet, Corridor, Whole House, etc.), only used when grouping by area, can be empty- Group titles in area mode display Chinese-English comparison (e.g.,
Living Room 客厅). Area names vary by project. The Agent dynamically translates them before each render per Step 5.7 and writes them intoregion_names; a built-in common area mapping serves only as a fallback;AREAis displayed if unmatched Unitis a single-select field, supports custom extensions- Logo supports both image (recommended) and text formats
- VAT rate is specified by the user each time, not fixed
- Output PDF is A4 size, suitable for printing
6. Common Problem Handling
| Error Phenomenon | Cause | Agent Handling Method |
|---|---|---|
command not found: lark-cli | lark-cli not installed | Prompt to install lark-cli, refer to Step 0.3 |
| lark-cli returns authentication error | Not logged in or token expired | Prompt to run lark-cli auth login |
Executable doesn't exist | Playwright Chromium not installed | Run npx playwright install chromium |
| Rendered PDF is empty or has formatting issues | Data JSON format incorrect | Check that Project Name, Engineering Number are non-empty, and items has at least 1 item |
| Detail serial numbers differ from manually entered ones | Serial numbers have been changed to auto-generation | Normal behavior: serial numbers are automatically renumbered by group (e.g., 01 Whole House → 1.1, 1.2…). To adjust order, drag records in Feishu |
| Font size/font appearance abnormal | Extremely rare (fonts are embedded as WOFF2, do not rely on external CDN) | Check that font files in the project references/fonts/ directory are complete |
| Logo download fails | Multidimensional table attachment API permission issue | Confirm user has authorized, or choose "Do not use Logo" |
base-token cannot be extracted from URL | User did not provide a multidimensional table link | Prompt the user to provide a Feishu multidimensional table link (URL should contain /base/) |