├── .github └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.md ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── index.js └── template │ ├── blocks │ └── row.html │ └── index.html ├── lib └── index.js └── package.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ionicabizau 2 | patreon: ionicabizau 3 | open_collective: ionicabizau 4 | custom: https://www.buymeacoffee.com/h96wwchmy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | *.log 5 | node_modules 6 | *.env 7 | .DS_Store 8 | package-lock.json 9 | .bloggify/* 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🌟 Contributing 2 | 3 | Want to contribute to this project? Great! Please read these quick steps to streamline the process and avoid unnecessary tasks. ✨ 4 | 5 | ## 💬 Discuss Changes 6 | Start by opening an issue in the repository using the [bug tracker][1]. Describe your proposed contribution or the bug you've found. If relevant, include platform info and screenshots. 🖼️ 7 | 8 | Wait for feedback before proceeding unless the fix is straightforward, like a typo. 📝 9 | 10 | ## 🔧 Fixing Issues 11 | 12 | Fork the project and create a branch for your fix, naming it `some-great-feature` or `some-issue-fix`. Commit changes while following the [code style][2]. If the project has tests, add one. ✅ 13 | 14 | If a `package.json` or `bower.json` exists, add yourself to the `contributors` array; create it if it doesn't. 🙌 15 | 16 | ```json 17 | { 18 | "contributors": [ 19 | "Your Name (http://your.website)" 20 | ] 21 | } 22 | ``` 23 | 24 | ## 📬 Creating a Pull Request 25 | Open a pull request and reference the initial issue (e.g., *fixes #*). Provide a clear title and consider adding visual aids for clarity. 📊 26 | 27 | ## ⏳ Wait for Feedback 28 | Your contributions will be reviewed. If feedback is given, update your branch as needed, and the pull request will auto-update. 🔄 29 | 30 | ## 🎉 Everyone Is Happy! 31 | Your contributions will be merged, and everyone will appreciate your effort! 😄❤️ 32 | 33 | Thanks! 🤩 34 | 35 | [1]: /issues 36 | [2]: https://github.com/IonicaBizau/code-style -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | You can see below the API reference of this module. 4 | 5 | ### `Invoice(options)` 6 | This is the constructor that creates a new instance containing the needed 7 | methods. 8 | 9 | #### Params 10 | 11 | - **Object** `options`: The options for creating the new invoice: 12 | - `config` (Object): 13 | - `template` (String): The HTML root template. 14 | - `data` (Object): 15 | - `currencyBalance` (Object): 16 | - `main` (Number): The main balance. 17 | - `secondary` (Number): The converted main balance. 18 | - `tasks` (Array): An array with the tasks (description of the services you did). 19 | - `invoice` (Object): Information about invoice. 20 | - `seller` (Object): Information about seller. 21 | - `buyer` (Object): Information about buyer. 22 | 23 | ### `initTemplates(callback)` 24 | Inits the HTML templates. 25 | 26 | #### Params 27 | 28 | - **Function** `callback`: The callback function. 29 | 30 | ### `toHtml(output, callback)` 31 | Renders the invoice in HTML format. 32 | 33 | #### Params 34 | 35 | - **String** `output`: An optional path to the output file. 36 | - **Function** `callback`: The callback function. 37 | 38 | #### Return 39 | - **Invoice** The `Nodeice` instance. 40 | 41 | ### `convertToSecondary(input)` 42 | Converts a currency into another currency according to the currency 43 | balance provided in the options 44 | 45 | #### Params 46 | 47 | - **Number** `input`: The number that should be converted 48 | 49 | #### Return 50 | - **Number** The converted input 51 | 52 | ### `toPdf(options, callback)` 53 | Renders invoice as pdf 54 | 55 | #### Params 56 | 57 | - **Object|String|Stream** `options`: The path the output pdf file, the stream object, or an object containing: 58 | 59 | - `output` (String|Stream): The path to the output file or the stream object. 60 | - `converter` (Object): An object containing custom settings for the [`phantom-html-to-pdf`](https://github.com/pofider/phantom-html-to-pdf). 61 | - **Function** `callback`: The callback function 62 | 63 | #### Return 64 | - **Invoice** The Invoice instance 65 | 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-25 Ionică Bizău (https://ionicabizau.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | [![nodeice](http://i.imgur.com/NuF1OI0.png)](#) 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | # nodeice 23 | 24 | [![Support me on Patreon][badge_patreon]][patreon] [![Buy me a book][badge_amazon]][amazon] [![PayPal][badge_paypal_donate]][paypal-donations] [![Ask me anything](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](https://github.com/IonicaBizau/ama) [![Version](https://img.shields.io/npm/v/nodeice.svg)](https://www.npmjs.com/package/nodeice) [![Downloads](https://img.shields.io/npm/dt/nodeice.svg)](https://www.npmjs.com/package/nodeice) [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/@johnnyb?utm_source=github&utm_medium=button&utm_term=johnnyb&utm_campaign=github) 25 | 26 | Buy Me A Coffee 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | > Another PDF invoice generator 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | [![nodeice](http://i.imgur.com/WnUnlFt.png)](#) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ## :cloud: Installation 55 | 56 | ```sh 57 | # Using npm 58 | npm install --save nodeice 59 | 60 | # Using yarn 61 | yarn add nodeice 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ## :clipboard: Example 77 | 78 | 79 | 80 | ```js 81 | const Invoice = require("nodeice"); 82 | 83 | // Create the new invoice 84 | let myInvoice = new Invoice({ 85 | config: { 86 | template: __dirname + "/template/index.html" 87 | , tableRowBlock: __dirname + "/template/blocks/row.html" 88 | } 89 | , data: { 90 | currencyBalance: { 91 | main: 1 92 | , secondary: 3.67 93 | } 94 | , invoice: { 95 | number: { 96 | series: "PREFIX" 97 | , separator: "-" 98 | , id: 1 99 | } 100 | , date: "01/02/2014" 101 | , dueDate: "11/02/2014" 102 | , explanation: "Thank you for your business!" 103 | , currency: { 104 | main: "XXX" 105 | , secondary: "ZZZ" 106 | } 107 | } 108 | , tasks: [ 109 | { 110 | description: "Some interesting task" 111 | , unit: "Hours" 112 | , quantity: 5 113 | , unitPrice: 2 114 | } 115 | , { 116 | description: "Another interesting task" 117 | , unit: "Hours" 118 | , quantity: 10 119 | , unitPrice: 3 120 | } 121 | , { 122 | description: "The most interesting one" 123 | , unit: "Hours" 124 | , quantity: 3 125 | , unitPrice: 5 126 | } 127 | ] 128 | } 129 | , seller: { 130 | company: "My Company Inc." 131 | , registrationNumber: "F05/XX/YYYY" 132 | , taxId: "00000000" 133 | , address: { 134 | street: "The Street Name" 135 | , number: "00" 136 | , zip: "000000" 137 | , city: "Some City" 138 | , region: "Some Region" 139 | , country: "Nowhere" 140 | } 141 | , phone: "+40 726 xxx xxx" 142 | , email: "me@example.com" 143 | , website: "example.com" 144 | , bank: { 145 | name: "Some Bank Name" 146 | , swift: "XXXXXX" 147 | , currency: "XXX" 148 | , iban: "..." 149 | } 150 | } 151 | , buyer: { 152 | company: "Another Company GmbH" 153 | , taxId: "00000000" 154 | , address: { 155 | street: "The Street Name" 156 | , number: "00" 157 | , zip: "000000" 158 | , city: "Some City" 159 | , region: "Some Region" 160 | , country: "Nowhere" 161 | } 162 | , phone: "+40 726 xxx xxx" 163 | , email: "me@example.com" 164 | , website: "example.com" 165 | , bank: { 166 | name: "Some Bank Name" 167 | , swift: "XXXXXX" 168 | , currency: "XXX" 169 | , iban: "..." 170 | } 171 | } 172 | }); 173 | 174 | // Render invoice as HTML and PDF 175 | myInvoice.toHtml(__dirname + "/my-invoice.html", (err, data) => { 176 | console.log("Saved HTML file"); 177 | }).toPdf(__dirname + "/my-invoice.pdf", (err, data) => { 178 | console.log("Saved pdf file"); 179 | }); 180 | 181 | // Serve the pdf via streams (no files) 182 | require("http").createServer((req, res) => { 183 | myInvoice.toPdf({ output: res }); 184 | }).listen(8000); 185 | ``` 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | ## :question: Get Help 199 | 200 | There are few ways to get help: 201 | 202 | 203 | 204 | 1. Please [post questions on Stack Overflow](https://stackoverflow.com/questions/ask). You can open issues with questions, as long you add a link to your Stack Overflow question. 205 | 2. For bug reports and feature requests, open issues. :bug: 206 | 3. For direct and quick help, you can [use Codementor](https://www.codementor.io/johnnyb). :rocket: 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | ## :memo: Documentation 215 | 216 | 217 | ### `Invoice(options)` 218 | This is the constructor that creates a new instance containing the needed 219 | methods. 220 | 221 | #### Params 222 | 223 | - **Object** `options`: The options for creating the new invoice: 224 | - `config` (Object): 225 | - `template` (String): The HTML root template. 226 | - `data` (Object): 227 | - `currencyBalance` (Object): 228 | - `main` (Number): The main balance. 229 | - `secondary` (Number): The converted main balance. 230 | - `tasks` (Array): An array with the tasks (description of the services you did). 231 | - `invoice` (Object): Information about invoice. 232 | - `seller` (Object): Information about seller. 233 | - `buyer` (Object): Information about buyer. 234 | 235 | ### `initTemplates(callback)` 236 | Inits the HTML templates. 237 | 238 | #### Params 239 | 240 | - **Function** `callback`: The callback function. 241 | 242 | ### `toHtml(output, callback)` 243 | Renders the invoice in HTML format. 244 | 245 | #### Params 246 | 247 | - **String** `output`: An optional path to the output file. 248 | - **Function** `callback`: The callback function. 249 | 250 | #### Return 251 | - **Invoice** The `Nodeice` instance. 252 | 253 | ### `convertToSecondary(input)` 254 | Converts a currency into another currency according to the currency 255 | balance provided in the options 256 | 257 | #### Params 258 | 259 | - **Number** `input`: The number that should be converted 260 | 261 | #### Return 262 | - **Number** The converted input 263 | 264 | ### `toPdf(options, callback)` 265 | Renders invoice as pdf 266 | 267 | #### Params 268 | 269 | - **Object|String|Stream** `options`: The path the output pdf file, the stream object, or an object containing: 270 | 271 | - `output` (String|Stream): The path to the output file or the stream object. 272 | - `converter` (Object): An object containing custom settings for the [`phantom-html-to-pdf`](https://github.com/pofider/phantom-html-to-pdf). 273 | - **Function** `callback`: The callback function 274 | 275 | #### Return 276 | - **Invoice** The Invoice instance 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | ## :yum: How to contribute 292 | Have an idea? Found a bug? See [how to contribute][contributing]. 293 | 294 | 295 | ## :sparkling_heart: Support my projects 296 | I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, 297 | this takes time. You can integrate and use these projects in your applications *for free*! You can even change the source code and redistribute (even resell it). 298 | 299 | However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: 300 | 301 | 302 | - Starring and sharing the projects you like :rocket: 303 | - [![Buy me a book][badge_amazon]][amazon]—I love books! I will remember you after years if you buy me one. :grin: :book: 304 | - [![PayPal][badge_paypal]][paypal-donations]—You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: 305 | - [![Support me on Patreon][badge_patreon]][patreon]—Set up a recurring monthly donation and you will get interesting news about what I'm doing (things that I don't share with everyone). 306 | - **Bitcoin**—You can send me bitcoins at this address (or scanning the code below): `1P9BRsmazNQcuyTxEqveUsnf5CERdq35V6` 307 | 308 | ![](https://i.imgur.com/z6OQI95.png) 309 | 310 | 311 | Thanks! :heart: 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | ## :dizzy: Where is this library used? 329 | If you are using this library in one of your projects, add it in this list. :sparkles: 330 | 331 | - `bloggify-shop` 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | ## :scroll: License 344 | 345 | [MIT][license] © [Ionică Bizău][website] 346 | 347 | 348 | 349 | 350 | 351 | 352 | [license]: /LICENSE 353 | [website]: https://ionicabizau.net 354 | [contributing]: /CONTRIBUTING.md 355 | [docs]: /DOCUMENTATION.md 356 | [badge_patreon]: https://ionicabizau.github.io/badges/patreon.svg 357 | [badge_amazon]: https://ionicabizau.github.io/badges/amazon.svg 358 | [badge_paypal]: https://ionicabizau.github.io/badges/paypal.svg 359 | [badge_paypal_donate]: https://ionicabizau.github.io/badges/paypal_donate.svg 360 | [patreon]: https://www.patreon.com/ionicabizau 361 | [amazon]: http://amzn.eu/hRo9sIZ 362 | [paypal-donations]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RVXDDLKKLQRJW 363 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | my-invoice* 2 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Invoice = require("../lib"); 4 | 5 | // Create the new invoice 6 | let myInvoice = new Invoice({ 7 | config: { 8 | template: __dirname + "/template/index.html" 9 | , tableRowBlock: __dirname + "/template/blocks/row.html" 10 | } 11 | , data: { 12 | currencyBalance: { 13 | main: 1 14 | , secondary: 3.67 15 | } 16 | , invoice: { 17 | number: { 18 | series: "PREFIX" 19 | , separator: "-" 20 | , id: 1 21 | } 22 | , date: "01/02/2014" 23 | , dueDate: "11/02/2014" 24 | , explanation: "Thank you for your business!" 25 | , currency: { 26 | main: "XXX" 27 | , secondary: "ZZZ" 28 | } 29 | } 30 | , tasks: [ 31 | { 32 | description: "Some interesting task" 33 | , unit: "Hours" 34 | , quantity: 5 35 | , unitPrice: 2 36 | } 37 | , { 38 | description: "Another interesting task" 39 | , unit: "Hours" 40 | , quantity: 10 41 | , unitPrice: 3 42 | } 43 | , { 44 | description: "The most interesting one" 45 | , unit: "Hours" 46 | , quantity: 3 47 | , unitPrice: 5 48 | } 49 | ] 50 | } 51 | , seller: { 52 | company: "My Company Inc." 53 | , registrationNumber: "F05/XX/YYYY" 54 | , taxId: "00000000" 55 | , address: { 56 | street: "The Street Name" 57 | , number: "00" 58 | , zip: "000000" 59 | , city: "Some City" 60 | , region: "Some Region" 61 | , country: "Nowhere" 62 | } 63 | , phone: "+40 726 xxx xxx" 64 | , email: "me@example.com" 65 | , website: "example.com" 66 | , bank: { 67 | name: "Some Bank Name" 68 | , swift: "XXXXXX" 69 | , currency: "XXX" 70 | , iban: "..." 71 | } 72 | } 73 | , buyer: { 74 | company: "Another Company GmbH" 75 | , taxId: "00000000" 76 | , address: { 77 | street: "The Street Name" 78 | , number: "00" 79 | , zip: "000000" 80 | , city: "Some City" 81 | , region: "Some Region" 82 | , country: "Nowhere" 83 | } 84 | , phone: "+40 726 xxx xxx" 85 | , email: "me@example.com" 86 | , website: "example.com" 87 | , bank: { 88 | name: "Some Bank Name" 89 | , swift: "XXXXXX" 90 | , currency: "XXX" 91 | , iban: "..." 92 | } 93 | } 94 | }); 95 | 96 | // Render invoice as HTML and PDF 97 | myInvoice.toHtml(__dirname + "/my-invoice.html", (err, data) => { 98 | console.log("Saved HTML file"); 99 | }).toPdf(__dirname + "/my-invoice.pdf", (err, data) => { 100 | console.log("Saved pdf file"); 101 | }); 102 | 103 | // Serve the pdf via streams (no files) 104 | require("http").createServer((req, res) => { 105 | myInvoice.toPdf({ output: res }); 106 | }).listen(8000); 107 | -------------------------------------------------------------------------------- /example/template/blocks/row.html: -------------------------------------------------------------------------------- 1 | 2 | {{nrCrt}} 3 | {{description}} 4 | {{unit}} 5 | {{quantity}} 6 | {{unitPrice.main}}
7 | {{unitPrice.secondary}} 8 | 9 | {{amount.main}}
10 | {{amount.secondary}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 68 | 69 | 70 |
71 | 72 | 73 |

{{seller.company}}

74 |

Tax Payer's Registration Number: {{seller.registrationNumber}}

75 |

Tax ID: {{seller.taxId}}

76 |

Address: {{seller.address.street}} {{seller.address.number}}
77 | {{seller.address.zip}} {{seller.address.city}}, {{seller.address.region}}, {{seller.address.country}}

78 |

Phone: {{seller.phone}}

79 |

Email: {{seller.email}}

80 |

Website: {{seller.website}}

81 |

Bank: {{seller.bank.name}}

82 |

SWIFT: {{seller.bank.swift}}

83 |

IBAN {{seller.bank.currency}}: {{seller.bank.iban}}

84 | 85 |
86 | 87 | 88 |

INVOICE / FACTURĂ

89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
Invoice-Number:{{invoice.number.series}}{{invoice.number.separator}}{{invoice.number.id}}
Invoice-Date (dd/mm/yyyy):{{invoice.date}}
Due-Date (dd/mm/yyyy):{{invoice.dueDate}}
105 | 106 | 107 |

Buyer / Cumpărător:

108 |

{{buyer.company}}

109 |

Tax ID: {{buyer.taxId}}

110 |

Address: {{buyer.address.street}} {{buyer.address.number}}
111 | {{buyer.address.zip}} {{buyer.address.city}}, {{buyer.address.country}}

112 |

Phone: {{buyer.phone}}

113 |

Email: {{buyer.email}}

114 |

Website: {{buyer.website}}

115 | 116 | 117 | 118 | 119 | 120 | 124 | 128 | 132 | 138 | 144 | 145 | 146 | 147 | {{{description_rows}}} 148 | 149 | 153 | 157 | 158 | 159 |
Nr. crt 121 | Description
122 | (Denumirea produselor sau a serviciilor) 123 |
125 | Unit
126 | (U. M.) 127 |
129 | Qty.
130 | (Cant.) 131 |
133 | Unit price.
134 | –{{invoice.currency.main}}– 135 |

136 | –({{invoice.currency.secondary}})– 137 |
139 | Total Amount.
140 | –{{invoice.currency.main}}– 141 |

142 | –({{invoice.currency.secondary}})– 143 |
150 | Invoice Total –CHF–
151 | (Valoare totală de plată factura curentă –RON–) 152 |
154 | {{total.main}}
155 | ({{total.secondary}}) 156 |
160 | {{invoice.explanation}} 161 |
162 | 163 | 164 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs") 4 | , readFile = require("read-utf8") 5 | , mustache = require("mustache") 6 | , sameTime = require("same-time") 7 | , oneByOne = require("one-by-one") 8 | , iterateObject = require("iterate-object") 9 | , noop = require("noop6") 10 | , EventEmitter = require("events").EventEmitter 11 | , ul = require("ul") 12 | , htmlPdf = require("phantom-html-to-pdf")({ phantomPath: require("phantomjs-prebuilt").path }) 13 | , isStream = require("is-stream") 14 | ; 15 | 16 | /** 17 | * Invoice 18 | * This is the constructor that creates a new instance containing the needed 19 | * methods. 20 | * 21 | * @name Invoice 22 | * @function 23 | * @param {Object} options The options for creating the new invoice: 24 | * 25 | * - `config` (Object): 26 | * - `template` (String): The HTML root template. 27 | * - `data` (Object): 28 | * - `currencyBalance` (Object): 29 | * - `main` (Number): The main balance. 30 | * - `secondary` (Number): The converted main balance. 31 | * - `tasks` (Array): An array with the tasks (description of the services you did). 32 | * - `invoice` (Object): Information about invoice. 33 | * - `seller` (Object): Information about seller. 34 | * - `buyer` (Object): Information about buyer. 35 | */ 36 | module.exports = class NodeIce { 37 | 38 | constructor (options) { 39 | this.options = options; 40 | this.templates = {}; 41 | } 42 | 43 | /** 44 | * initTemplates 45 | * Inits the HTML templates. 46 | * 47 | * @name initTemplates 48 | * @function 49 | * @param {Function} callback The callback function. 50 | */ 51 | initTemplates (callback) { 52 | if (this.templates.root === undefined || this.templates.tableRowBlock === undefined) { 53 | sameTime([ 54 | cb => readFile(this.options.config.template, cb) 55 | , cb => readFile(this.options.config.tableRowBlock, cb) 56 | ], (err, data) => { 57 | if (err) { return callback(err); } 58 | this.templates.root = data[0]; 59 | this.templates.tableRowBlock = data[1]; 60 | callback(null, this.templates); 61 | }); 62 | } else { 63 | return callback(null, this.templates); 64 | } 65 | } 66 | 67 | /** 68 | * toHtml 69 | * Renders the invoice in HTML format. 70 | * 71 | * @name toHtml 72 | * @function 73 | * @param {String} output An optional path to the output file. 74 | * @param {Function} callback The callback function. 75 | * @return {Invoice} The `Nodeice` instance. 76 | */ 77 | toHtml (output, callback) { 78 | 79 | if (typeof output === "function") { 80 | callback = output; 81 | output = null; 82 | } 83 | 84 | let options = this.options 85 | , tasks = options.data.tasks 86 | , invoiceHtml = "" 87 | , invoiceData = { 88 | seller: options.seller 89 | , buyer: options.buyer 90 | , invoice: options.data.invoice 91 | , description_rows: "" 92 | , total: { 93 | main: 0 94 | , secondary: 0 95 | } 96 | } 97 | ; 98 | 99 | this.initTemplates((err, templates) => { 100 | if (err) { return callback(err); } 101 | 102 | iterateObject(tasks, (cTask, i) => { 103 | // Set the additional fields and compute data 104 | cTask.nrCrt = i + 1; 105 | if (typeof cTask.unitPrice === "number") { 106 | cTask.unitPrice = { 107 | main: cTask.unitPrice 108 | , secondary: this.convertToSecondary( 109 | cTask.unitPrice 110 | ) 111 | }; 112 | } 113 | 114 | if (typeof cTask.unitPrice.main === "number") { 115 | // Set the unit price of this row 116 | cTask.unitPrice.main = cTask.unitPrice.main.toFixed(2); 117 | cTask.unitPrice.secondary = cTask.unitPrice.secondary.toFixed(2); 118 | } 119 | 120 | // Build amount object 121 | cTask.amount = { 122 | main: cTask.unitPrice.main * cTask.quantity 123 | , secondary: cTask.unitPrice.secondary * cTask.quantity 124 | }; 125 | 126 | // Sum the amount to the total 127 | invoiceData.total.main += cTask.amount.main; 128 | invoiceData.total.secondary += cTask.amount.secondary; 129 | 130 | // Set the amount of this row 131 | cTask.amount.main = cTask.amount.main.toFixed(2); 132 | cTask.amount.secondary = cTask.amount.secondary.toFixed(2); 133 | 134 | // Render HTML for the current row 135 | invoiceData.description_rows += mustache.render( 136 | templates.tableRowBlock, cTask 137 | ); 138 | }); 139 | 140 | // Set the total 141 | invoiceData.total.main = invoiceData.total.main.toFixed(2); 142 | invoiceData.total.secondary = invoiceData.total.secondary.toFixed(2); 143 | 144 | // Render the invoice HTML fields 145 | invoiceHtml = mustache.render(templates.root, invoiceData); 146 | 147 | // Output file 148 | if (typeof output === "string") { 149 | fs.writeFile(output, invoiceHtml, err => { 150 | callback(err, invoiceHtml); 151 | }); 152 | return; 153 | } 154 | 155 | // Callback the data 156 | callback(null, invoiceHtml); 157 | }); 158 | 159 | return this; 160 | } 161 | 162 | /** 163 | * convertToSecondary 164 | * Converts a currency into another currency according to the currency 165 | * balance provided in the options 166 | * 167 | * @name convertToSecondary 168 | * @function 169 | * @param {Number} input The number that should be converted 170 | * @return {Number} The converted input 171 | */ 172 | convertToSecondary (input) { 173 | return input * this.options.data.currencyBalance.secondary / this.options.data.currencyBalance.main; 174 | } 175 | 176 | /** 177 | * toPdf 178 | * Renders invoice as pdf 179 | * 180 | * @name toPdf 181 | * @function 182 | * @param {Object|String|Stream} options The path the output pdf file, the 183 | * stream object, or an object containing: 184 | * 185 | * - `output` (String|Stream): The path to the output file or the stream object. 186 | * - `converter` (Object): An object containing custom settings for the [`phantom-html-to-pdf`](https://github.com/pofider/phantom-html-to-pdf). 187 | * 188 | * @param {Function} callback The callback function 189 | * @return {Invoice} The Invoice instance 190 | */ 191 | toPdf (ops, callback) { 192 | 193 | let ev = new EventEmitter() 194 | , opsIsStream = isStream(ops) 195 | , noStream = false 196 | ; 197 | 198 | callback = callback || noop; 199 | if (typeof ops === "function") { 200 | callback = ops; 201 | ops = {}; 202 | } 203 | 204 | 205 | if (typeof ops === "string" || opsIsStream) { 206 | ops = { output: ops }; 207 | } 208 | 209 | if (!opsIsStream && typeof ops.output === "string") { 210 | ops.output = fs.createWriteStream(ops.output); 211 | } 212 | 213 | noStream = !isStream(ops.output); 214 | 215 | ops = ul.deepMerge(ops, { 216 | converter: { 217 | viewportSize: { 218 | width: 2480 219 | , height: 3508 220 | }, 221 | paperSize: { 222 | format: "A3" 223 | } 224 | } 225 | }); 226 | 227 | oneByOne([ 228 | this.toHtml.bind(this) 229 | , (next, html) => { 230 | ops.converter.html = html; 231 | htmlPdf(ops.converter, next); 232 | } 233 | , (next, pdf) => { 234 | 235 | if (noStream) { 236 | return next(null, pdf); 237 | } 238 | 239 | let err = []; 240 | ops.output.on("error", err => err.push(err)); 241 | pdf.stream.on("end", () => { 242 | if (err.length) { 243 | return next(err.length === 1 ? err[0] : err); 244 | } 245 | next(null, pdf); 246 | }); 247 | pdf.stream.pipe(ops.output); 248 | } 249 | ], (err, data) => { 250 | callback(err, data[1], data[0]); 251 | }); 252 | } 253 | }; 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeice", 3 | "version": "3.0.11", 4 | "description": "Another PDF invoice generator", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "node test/index" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:IonicaBizau/nodeice.git" 12 | }, 13 | "keywords": [ 14 | "invoice", 15 | "node", 16 | "javascript", 17 | "pdf" 18 | ], 19 | "author": "Ionică Bizău (https://ionicabizau.net)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/IonicaBizau/nodeice/issues" 23 | }, 24 | "homepage": "https://github.com/IonicaBizau/nodeice", 25 | "directories": { 26 | "test": "test" 27 | }, 28 | "devDependencies": {}, 29 | "blah": { 30 | "h_img": "http://i.imgur.com/NuF1OI0.png", 31 | "ex_img": "http://i.imgur.com/WnUnlFt.png" 32 | }, 33 | "dependencies": { 34 | "is-stream": "^1.1.0", 35 | "iterate-object": "^1.3.2", 36 | "mustache": "^2.3.0", 37 | "noop6": "^1.0.7", 38 | "one-by-one": "^3.2.6", 39 | "phantom-html-to-pdf": "^0.5.5", 40 | "phantomjs-prebuilt": "^2.1.16", 41 | "read-utf8": "^1.2.8", 42 | "same-time": "^2.3.3", 43 | "ul": "^5.2.13" 44 | }, 45 | "files": [ 46 | "bin/", 47 | "app/", 48 | "lib/", 49 | "dist/", 50 | "src/", 51 | "scripts/", 52 | "resources/", 53 | "menu/", 54 | "cli.js", 55 | "index.js", 56 | "index.d.ts", 57 | "package-lock.json", 58 | "bloggify.js", 59 | "bloggify.json", 60 | "bloggify/" 61 | ] 62 | } --------------------------------------------------------------------------------