UNPKG

n8n-nodes-lhdn-einvoice

Version:

n8n node untuk integrasi dengan LHDN e-Invoice API Malaysia

816 lines (815 loc) 35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LhdnEInvoice = void 0; const GenericFunctions_1 = require("./GenericFunctions"); class LhdnEInvoice { constructor() { this.description = { displayName: 'LHDN e-Invoice', name: 'lhdnEInvoice', icon: 'file:lhdn-einvoice.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Berinteraksi dengan API LHDN e-Invoice Malaysia', defaults: { name: 'LHDN e-Invoice', }, inputs: ["main"], outputs: ["main"], credentials: [ { name: 'lhdnEInvoiceApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Authentication', value: 'authentication', }, { name: 'Document', value: 'document', }, { name: 'Validation', value: 'validation', }, ], default: 'document', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['authentication'], }, }, options: [ { name: 'Get Token', value: 'getToken', description: 'Dapatkan access token', action: 'Get access token', }, ], default: 'getToken', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['document'], }, }, options: [ { name: 'Submit', value: 'submit', description: 'Hantar dokumen e-invoice', action: 'Submit document', }, { name: 'Get', value: 'get', description: 'Dapatkan dokumen berdasarkan UUID', action: 'Get document', }, { name: 'Get Recent', value: 'getRecent', description: 'Dapatkan dokumen terkini', action: 'Get recent documents', }, { name: 'Cancel', value: 'cancel', description: 'Batalkan dokumen', action: 'Cancel document', }, { name: 'Reject', value: 'reject', description: 'Tolak dokumen', action: 'Reject document', }, { name: 'Get Status', value: 'getStatus', description: 'Dapatkan status dokumen', action: 'Get document status', }, ], default: 'submit', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['validation'], }, }, options: [ { name: 'Validate TIN', value: 'validateTin', description: 'Sahkan TIN (Tax Identification Number)', action: 'Validate TIN', }, ], default: 'validateTin', }, { displayName: 'Input Mode', name: 'inputMode', type: 'options', displayOptions: { show: { resource: ['document'], operation: ['submit'], }, }, options: [ { name: 'Form Fields (Easy)', value: 'form', description: 'Isi borang dengan field-field yang mudah', }, { name: 'Raw Document (Advanced)', value: 'raw', description: 'Masukkan dokumen JSON/XML secara manual', }, ], default: 'form', description: 'Pilih cara untuk memasukkan data invoice', }, { displayName: 'Invoice Number', name: 'invoiceNumber', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Nombor invoice (contoh: INV001)', }, { displayName: 'Invoice Type', name: 'invoiceType', type: 'options', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, options: [ { name: 'Invoice', value: '01', description: 'Invoice biasa', }, { name: 'Credit Note', value: '02', description: 'Nota kredit', }, { name: 'Debit Note', value: '03', description: 'Nota debit', }, { name: 'Refund Note', value: '11', description: 'Nota bayaran balik', }, ], default: '01', description: 'Jenis dokumen invoice', }, { displayName: 'Currency', name: 'currency', type: 'options', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, options: [ { name: 'MYR (Malaysian Ringgit)', value: 'MYR', }, { name: 'USD (US Dollar)', value: 'USD', }, { name: 'SGD (Singapore Dollar)', value: 'SGD', }, { name: 'EUR (Euro)', value: 'EUR', }, ], default: 'MYR', description: 'Mata wang dokumen', }, { displayName: 'Supplier TIN', name: 'supplierTin', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'TIN syarikat pengeluar (contoh: C12345678)', }, { displayName: 'Supplier Name', name: 'supplierName', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Nama syarikat pengeluar', }, { displayName: 'Supplier Address', name: 'supplierAddress', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Alamat syarikat pengeluar', }, { displayName: 'Supplier City', name: 'supplierCity', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Bandar syarikat pengeluar', }, { displayName: 'Supplier Postal Code', name: 'supplierPostalCode', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Poskod syarikat pengeluar', }, { displayName: 'Supplier State', name: 'supplierState', type: 'options', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, options: [ { name: 'Johor', value: '01' }, { name: 'Kedah', value: '02' }, { name: 'Kelantan', value: '03' }, { name: 'Melaka', value: '04' }, { name: 'Negeri Sembilan', value: '05' }, { name: 'Pahang', value: '06' }, { name: 'Pulau Pinang', value: '07' }, { name: 'Perak', value: '08' }, { name: 'Perlis', value: '09' }, { name: 'Selangor', value: '10' }, { name: 'Terengganu', value: '11' }, { name: 'Sabah', value: '12' }, { name: 'Sarawak', value: '13' }, { name: 'Kuala Lumpur', value: '14' }, { name: 'Labuan', value: '15' }, { name: 'Putrajaya', value: '16' }, ], default: '14', required: true, description: 'Negeri syarikat pengeluar', }, { displayName: 'Customer TIN', name: 'customerTin', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'TIN pelanggan (contoh: D98765432)', }, { displayName: 'Customer Name', name: 'customerName', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', required: true, description: 'Nama pelanggan', }, { displayName: 'Customer Address', name: 'customerAddress', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', description: 'Alamat pelanggan (optional)', }, { displayName: 'Customer City', name: 'customerCity', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: '', description: 'Bandar pelanggan (optional)', }, { displayName: 'Invoice Items', name: 'invoiceItems', type: 'fixedCollection', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['form'], }, }, default: {}, typeOptions: { multipleValues: true, }, description: 'Item-item dalam invoice', options: [ { name: 'item', displayName: 'Item', values: [ { displayName: 'Description', name: 'description', type: 'string', default: '', required: true, description: 'Penerangan item', }, { displayName: 'Quantity', name: 'quantity', type: 'number', default: 1, required: true, description: 'Kuantiti item', }, { displayName: 'Unit Price', name: 'unitPrice', type: 'number', default: 0, required: true, description: 'Harga per unit', }, { displayName: 'Tax Rate (%)', name: 'taxRate', type: 'number', default: 6, description: 'Kadar cukai (default: 6% SST)', }, { displayName: 'Tax Type', name: 'taxType', type: 'options', options: [ { name: 'Standard Rate (6%)', value: '01', }, { name: 'Zero Rate (0%)', value: '02', }, { name: 'Exempt', value: '03', }, ], default: '01', description: 'Jenis cukai', }, ], }, ], }, { displayName: 'Document Format', name: 'documentFormat', type: 'options', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['raw'], }, }, options: [ { name: 'JSON', value: 'JSON', }, { name: 'XML', value: 'XML', }, ], default: 'JSON', description: 'Format dokumen yang akan dihantar', }, { displayName: 'Document', name: 'document', type: 'json', displayOptions: { show: { resource: ['document'], operation: ['submit'], inputMode: ['raw'], }, }, default: '', required: true, description: 'Dokumen e-invoice dalam format JSON atau XML', }, { displayName: 'Code Number', name: 'codeNumber', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['submit'], }, }, default: '', required: true, description: 'Kod nombor untuk dokumen', }, { displayName: 'Document UUID', name: 'documentUuid', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['get', 'cancel', 'reject', 'getStatus'], }, }, default: '', required: true, description: 'UUID dokumen yang ingin diakses', }, { displayName: 'Page Number', name: 'pageNo', type: 'number', displayOptions: { show: { resource: ['document'], operation: ['getRecent'], }, }, default: 1, description: 'Nombor halaman untuk pagination', }, { displayName: 'Page Size', name: 'pageSize', type: 'number', displayOptions: { show: { resource: ['document'], operation: ['getRecent'], }, }, default: 20, description: 'Saiz halaman untuk pagination', }, { displayName: 'Submission Date From', name: 'submissionDateFrom', type: 'dateTime', displayOptions: { show: { resource: ['document'], operation: ['getRecent'], }, }, default: '', description: 'Tarikh mula untuk carian dokumen', }, { displayName: 'Submission Date To', name: 'submissionDateTo', type: 'dateTime', displayOptions: { show: { resource: ['document'], operation: ['getRecent'], }, }, default: '', description: 'Tarikh akhir untuk carian dokumen', }, { displayName: 'Reason', name: 'reason', type: 'string', displayOptions: { show: { resource: ['document'], operation: ['cancel', 'reject'], }, }, default: '', required: true, description: 'Sebab untuk pembatalan atau penolakan', }, { displayName: 'TIN', name: 'tin', type: 'string', displayOptions: { show: { resource: ['validation'], operation: ['validateTin'], }, }, default: '', required: true, description: 'Tax Identification Number untuk disahkan', }, { displayName: 'ID Type', name: 'idType', type: 'options', displayOptions: { show: { resource: ['validation'], operation: ['validateTin'], }, }, options: [ { name: 'NRIC', value: 'NRIC', }, { name: 'Passport', value: 'PASSPORT', }, { name: 'BRN', value: 'BRN', }, { name: 'Army', value: 'ARMY', }, ], default: 'NRIC', description: 'Jenis ID untuk pengesahan TIN', }, { displayName: 'ID Value', name: 'idValue', type: 'string', displayOptions: { show: { resource: ['validation'], operation: ['validateTin'], }, }, default: '', required: true, description: 'Nilai ID untuk pengesahan TIN', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); for (let i = 0; i < items.length; i++) { try { let responseData; if (resource === 'authentication') { if (operation === 'getToken') { responseData = await GenericFunctions_1.getAccessToken.call(this); } } else if (resource === 'document') { const accessToken = await GenericFunctions_1.getAccessToken.call(this); if (operation === 'submit') { const inputMode = this.getNodeParameter('inputMode', i); const codeNumber = this.getNodeParameter('codeNumber', i); let document; let documentFormat = 'JSON'; if (inputMode === 'form') { const formData = { invoiceNumber: this.getNodeParameter('invoiceNumber', i), invoiceType: this.getNodeParameter('invoiceType', i), currency: this.getNodeParameter('currency', i), supplierTin: this.getNodeParameter('supplierTin', i), supplierName: this.getNodeParameter('supplierName', i), supplierAddress: this.getNodeParameter('supplierAddress', i), supplierCity: this.getNodeParameter('supplierCity', i), supplierPostalCode: this.getNodeParameter('supplierPostalCode', i), supplierState: this.getNodeParameter('supplierState', i), customerTin: this.getNodeParameter('customerTin', i), customerName: this.getNodeParameter('customerName', i), customerAddress: this.getNodeParameter('customerAddress', i), customerCity: this.getNodeParameter('customerCity', i), invoiceItems: this.getNodeParameter('invoiceItems', i), }; const invoiceDocument = (0, GenericFunctions_1.buildInvoiceFromFormFields)(formData); document = JSON.stringify(invoiceDocument); documentFormat = 'JSON'; } else { documentFormat = this.getNodeParameter('documentFormat', i); document = this.getNodeParameter('document', i); } const documentBase64 = Buffer.from(document).toString('base64'); const crypto = require('crypto'); const documentHash = crypto.createHash('sha256').update(document).digest('hex'); const body = { documents: [ { format: documentFormat, documentHash: documentHash, codeNumber: codeNumber, document: documentBase64, }, ], }; responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'POST', '/api/v1.0/documentsubmissions', body, {}, accessToken.access_token); } else if (operation === 'get') { const documentUuid = this.getNodeParameter('documentUuid', i); responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'GET', `/api/v1.0/documents/${documentUuid}/raw`, {}, {}, accessToken.access_token); } else if (operation === 'getRecent') { const pageNo = this.getNodeParameter('pageNo', i); const pageSize = this.getNodeParameter('pageSize', i); const submissionDateFrom = this.getNodeParameter('submissionDateFrom', i); const submissionDateTo = this.getNodeParameter('submissionDateTo', i); const qs = { PageNo: pageNo, PageSize: pageSize, }; if (submissionDateFrom) { qs.SubmissionDateFrom = submissionDateFrom; } if (submissionDateTo) { qs.SubmissionDateTo = submissionDateTo; } responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'GET', '/api/v1.0/documents/recent', {}, qs, accessToken.access_token); } else if (operation === 'cancel') { const documentUuid = this.getNodeParameter('documentUuid', i); const reason = this.getNodeParameter('reason', i); const body = { status: 'cancelled', reason: reason, }; responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'PUT', `/api/v1.0/documents/state/${documentUuid}/state`, body, {}, accessToken.access_token); } else if (operation === 'reject') { const documentUuid = this.getNodeParameter('documentUuid', i); const reason = this.getNodeParameter('reason', i); const body = { status: 'rejected', reason: reason, }; responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'PUT', `/api/v1.0/documents/state/${documentUuid}/state`, body, {}, accessToken.access_token); } else if (operation === 'getStatus') { const documentUuid = this.getNodeParameter('documentUuid', i); responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'GET', `/api/v1.0/documents/${documentUuid}/details`, {}, {}, accessToken.access_token); } } else if (resource === 'validation') { if (operation === 'validateTin') { const accessToken = await GenericFunctions_1.getAccessToken.call(this); const tin = this.getNodeParameter('tin', i); const idType = this.getNodeParameter('idType', i); const idValue = this.getNodeParameter('idValue', i); responseData = await GenericFunctions_1.lhdnEInvoiceApiRequest.call(this, 'GET', `/api/v1.0/taxpayer/validate/${tin}`, {}, { idType: idType, idValue: idValue, }, accessToken.access_token); } } const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } }); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({ error: error.message }), { itemData: { item: i } }); returnData.push(...executionData); continue; } throw error; } } return [returnData]; } } exports.LhdnEInvoice = LhdnEInvoice;