Compare commits

..

8 Commits

Author SHA1 Message Date
roshelrao
945724ae3c invoice changes 2021-07-19 13:46:12 +05:30
ddave
72c8b4774c Adding the action manager for the extension 2021-07-16 08:17:30 +02:00
roshelrao
9a1ee350c3 Invoice PDF builder 2021-07-14 13:10:39 +05:30
ddave
7228bb2922 Pdf builder for invoices 2021-07-09 11:24:04 +02:00
roshelrao
5416bdb840 added missing fields 2021-07-08 15:03:32 +05:30
roshelrao
1332448d72 invoice module 2021-07-01 15:51:04 +05:30
Thilina
9f47df5613 Ability to compile extensions 2021-06-29 11:37:46 +02:00
Thilina
731073a133 Merge tag 'v30.0.0.OS' into develop
v30.0.0.OS
2021-06-28 11:48:07 +02:00
30 changed files with 423443 additions and 34137 deletions

49
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,49 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
},
{
"name": "Launch Built-in web server",
"type": "php",
"request": "launch",
"runtimeArgs": [
"-dxdebug.mode=debug",
"-dxdebug.start_with_request=yes",
"-S",
"localhost:0"
],
"program": "",
"cwd": "${workspaceRoot}",
"port": 9003,
"serverReadyAction": {
"pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
}
}
]
}

View File

@@ -0,0 +1,31 @@
<?php
use Classes\ExtensionManager;
use Utils\LogManager;
if (!isset($extensionIndex)) {
exit();
}
define('MODULE_PATH',APP_BASE_PATH.'extensions/'.$moduleName);
include APP_BASE_PATH.'header.php';
$extensionManager = new ExtensionManager();
$meta = $extensionManager->getExtensionMetaData($moduleName);
if (!$meta) {
LogManager::getInstance()->error("Extension metadata.json not found for $moduleName");
exit();
}
if ($meta->headless) {
LogManager::getInstance()->error("Extension running in headless mode for $moduleName");
exit();
}
?>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorReact.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntd.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntdIcons.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntv.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorOther.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=EXTENSIONS_URL.$moduleName.'/dist/'.$moduleName.'.js'?>?v=<?=$jsVersion?>"></script>
<?php
include $extensionIndex;
include APP_BASE_PATH.'footer.php';
?>

View File

@@ -2,7 +2,7 @@
namespace Classes\Pdf;
use Forms\Common\EmployeeFormPDFBuilder;
use Invoices\InvoicePDFBuilder;
class PDFRegister
{
@@ -10,8 +10,8 @@ class PDFRegister
public static function init()
{
self::put('empf', function ($data) {
return new EmployeeFormPDFBuilder($data);
self::put('invoice', function ($data) {
return new InvoicePDFBuilder($data);
});
}

1
extensions/gitkeep Executable file
View File

@@ -0,0 +1 @@
git keep

24757
extensions/invoices/dist/invoices.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
<?php
require_once __DIR__.'/src/Invoices/Extension.php';
require_once __DIR__.'/src/Invoices/Manager.php';
require_once __DIR__.'/src/Invoices/Migration.php';
require_once __DIR__.'/src/Invoices/Model/Invoice.php';

View File

@@ -0,0 +1,14 @@
{
"label": "Invoices",
"menu": ["Invoices", "fa-file"],
"icon": "fa-file-invoice",
"user_levels": [
"Admin",
"Manager",
"User"
],
"model_namespace": "\\Invoices\\Model",
"manager": "\\Invoices\\Extension",
"action": "\\Invoices\\Manager",
"headless": false
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Invoices;
use Classes\IceExtension;
class Extension extends IceExtension
{
public function install() {
$migration = new Migration();
return $migration->up();
}
public function uninstall() {
$migration = new Migration();
return $migration->down();
}
public function setupModuleClassDefinitions()
{
$this->addModelClass('Invoice');
}
public function setupRestEndPoints()
{
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Invoices;
use Classes\Authorizable;
use Classes\BaseService;
use Classes\IceResponse;
use Classes\Pdf\PdfBuilder;
use Invoices\Model\Invoice;
use Invoices\Pdf\InvoicePdf;
class InvoicePDFBuilder implements Authorizable, PdfBuilder
{
protected $formId;
public function __construct($formId)
{
$this->formId = $formId;
}
public function granted(): bool
{
$empForm = new EmployeeForm();
$empForm->Load("id = ?", array($this->formId));
$currentEmployeeId = BaseService::getInstance()->getCurrentProfileId();
$user = BaseService::getInstance()->getCurrentUser();
return ($currentEmployeeId == $empForm->employee
|| $user->user_level === 'Admin'
|| BaseService::getInstance()->isSubordinateEmployee($currentEmployeeId, $empForm->employee)
);
}
public function createPdf()
{
$response = FormsActionManager::getFormDataById($this->formId);
if ($response->getStatus() === IceResponse::ERROR) {
return null;
}
$form = $response->getData()['form'];
$invoiceForm = $response->getData()['data'];
$invoice = new Invoice();
$invoice->Load('id = ?', [$invoiceForm->invoice]);
//$employee = FileService::getInstance()->updateSmallProfileImage($employee);
$pdf = new InvoicePdf($invoice);
$pdf->initialize($form->name);
$pdf->process();
return $pdf;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Invoices;
use Classes\IceResponse;
use Classes\SubActionManager;
class Manager extends SubActionManager
{
public function printInvoice($req)
{
$id = $req->id;
return new IceResponse(IceResponse::SUCCESS, true);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Invoices;
use Classes\Migration\AbstractMigration;
class Migration extends AbstractMigration
{
public function up()
{
$sql = <<<'SQL'
create table Invoices
(
id bigint auto_increment primary key,
paymentId bigint not null,
invoiceId bigint not null,
description varchar(500) charset utf8 not null,
buyerName varchar(200) charset utf8 not null,
buyerAddress varchar(200) charset utf8 not null,
buyerPostalCode varchar(200) charset utf8 not null,
buyerCountry varchar(200) charset utf8 not null,
buyerVatId varchar(50) charset utf8 not null,
sellerName varchar(200) charset utf8 not null,
sellerAddress varchar(200) null,
sellerCountry varchar(200) charset utf8 not null,
sellerVatId varchar(50) charset utf8 not null,
amount decimal(10,2) default 0.00 null,
vat decimal(10,2) default 0.00 null,
vatRate decimal(10,2) default 0.00 null,
issuedDate datetime null,
dueDate datetime null,
paidDate datetime null,
created datetime null,
updated datetime null,
status enum('Pending', 'Paid', 'Processing', 'Draft', 'Sent', 'Canceled') collate utf8_unicode_ci default 'Pending' null,
acceptPayments tinyint default 0 null,
buyerEmail varchar(125) charset utf8 null,
items text charset utf8 null,
constraint invoiceId
unique (invoiceId)
)
collate=utf8mb4_unicode_ci;
SQL;
return $this->executeQuery($sql);
}
public function down()
{
$sql = <<<'SQL'
DROP TABLE IF EXISTS `Invoices`;
SQL;
return $this->executeQuery($sql);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Invoices\Model;
use Classes\ModuleAccess;
use Model\BaseModel;
class Invoice extends BaseModel
{
public $table = 'Invoices';
public function getAdminAccess()
{
return array("get","element","save","delete");
}
public function getModuleAccess()
{
return [
new ModuleAccess('employees', 'admin'),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Invoices\Pdf;
use Classes\Pdf\BasePdfTemplate;
use Invoices\Model\Invoice;
class InvoicePdf extends BasePdfTemplate
{
protected $invoice;
public function __construct(Invoice $invoice)
{
parent::__construct();
$this->invoice = $invoice;
}
public function process()
{
$this->addH3($this->form->name, 'B', 'L');
$this->addBorderedText($this->invoice->description);
}
// protected function addFormItems()
// {
// $signatures = [];
// $fields = json_decode($this->form->items);
// foreach ($fields as $field) {
// if ($field->field_type === 'signature') {
// $signatures[$field->field_label] = $this->employeeForm->{$field->name};
// } elseif ($field->field_type === 'fileupload') {
// // ignore
// } else {
// $this->addKeyValue($field->field_label, $this->employeeForm->{$field->name}, $field->field_type);
// }
// }
//
// foreach ($signatures as $name => $data) {
// $this->addSignature($name, $data);
// }
// }
}

View File

@@ -0,0 +1,38 @@
<?php
/*$user = \Classes\BaseService::getInstance()->getCurrentUser();
echo "Welcome ".$user->username."<br/>";
echo "Invoices <br/>";
*/
use Classes\PermissionManager;
use Invoices\Model\Invoice;
?><div class="span9">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabInvoices" href="#tabPageInvoices"><?=t('Invoices')?></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabPageInvoices">
<div id="InvoicesTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
<div id="InvoicesForm"></div>
<div id="InvoicesFilterForm"></div>
</div>
</div>
</div>
<div id="dataGroup"></div>
<?php
$moduleData = [
'user_level' => $user->user_level,
'permissions' => [
'Invoice' => PermissionManager::checkGeneralAccess(new Invoice()),
]
];
?>
<script>
initAdminInvoices(<?=json_encode($moduleData)?>);
</script>

View File

@@ -0,0 +1,17 @@
import {InvoiceAdapter} from './lib';
import IceDataPipe from '../../../../web/api/IceDataPipe';
function init(data) {
const modJsList = [];
modJsList.tabInvoices =new InvoiceAdapter('Invoice', 'Invoices','','');
modJsList.tabInvoices.setObjectTypeName('Invoice');
modJsList.tabInvoices.setDataPipe(new IceDataPipe(modJsList.tabInvoices));
modJsList.tabInvoices.setAccess(data.permissions.Invoice);
window.modJs = modJsList.tabInvoices;
window.modJsList = modJsList;
}
window.initAdminInvoices = init;

View File

@@ -0,0 +1,236 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Space, Tag } from 'antd';
import ReactModalAdapterBase from '../../../../web/api/ReactModalAdapterBase';
import {
EditOutlined, DeleteOutlined, CopyOutlined, MonitorOutlined, PrinterOutlined,
} from '@ant-design/icons';
/**
* VatInvoiceAdapter
*/
class InvoiceAdapter extends ReactModalAdapterBase {
getDataMapping() {
return [
'id',
'paymentId',
'invoiceId',
'description',
'buyerName',
'buyerAddress',
'buyerPostalAddress',
'buyerVatId',
'buyerEmail',
'sellerName',
'sellerAddress',
'sellerVatId',
'amount',
'vat',
'vatRate',
'issuedDate',
'dueDate',
'paidDate',
'status',
'acceptPayments',
'created',
'updated',
'link',
'paymentLink'
];
}
getHeaders() {
return [
{ sTitle: 'ID', bVisible: false },
{ sTitle: 'Payment Id' },
{ sTitle: 'Invoice ID' },
{ sTitle: 'Description' },
{ sTitle: 'Buyer Name' },
{ sTitle: 'Buyer Address' },
{ sTitle: 'Buyer Postal Code' },
{ sTitle: 'Buyer Country' },
{ sTitle: 'Buyer Vat Id' },
{ sTitle: 'Buyer Email' },
{ sTitle: 'Seller Name' },
{ sTitle: 'Seller Country' },
{ sTitle: 'Seller Vat Id' },
{ sTitle: 'Amount' },
{ sTitle: 'Vat' },
{ sTitle: 'Vat Rate' },
{ sTitle: 'Issued Date' },
{ sTitle: 'Paid Date' },
{ sTitle: 'Status' },
{ sTitle: 'Accept Payments' },
{ sTitle: 'Created' },
{ sTitle: 'Updated' },
{ sTitle: 'Link' },
{ sTitle: 'Payment Link' },
];
}
getCountryList() {
return [
['DE', 'Germany'],
['LK', 'Sri Lanka'],
];
}
getFormFields() {
return [
[ 'id', {"label":"ID","type":"hidden"}],
[ 'paymentId', {"label":"Payment Id","type":"text","validation":"int"}],
[ 'invoiceId', {"label":"Invoice Id","type":"text","validation":"int"}],
[ 'description', {"label":"Description","type":"textarea","validation":"none"}],
[ 'buyerName', {"label":"Buyer Name","type":"text"}],
[ 'buyerAddress', {"label":"Buyer Address","type":"textarea"}],
[ 'buyerPostalCode', {"label":"Buyer Postal Code","type":"text"}],
[ 'buyerCountry', {"label":"Buyer Country","type":"select2", "remote-source": ["Country", "code", "name"]}],
[ 'buyerVatId', {"label":"Buyer Vat Id","type":"text","validation":"none"}],
[ 'buyerEmail', {"label":"Buyer Email","type":"text","validation":"email"}],
[ 'sellerName', {"label":"Seller Name","type":"text"}],
[ 'sellerAddress', {"label":"Seller Address","type":"text"}],
[ 'sellerCountry', {"label":"Seller Country","type":"select2", "remote-source": ["Country", "code", "name"]}],
[ 'sellerVatId', {"label":"Seller Vat Id","type":"text"}],
[ 'amount', {"label":"Amount with VAT","type":"text", "validation":"float"}],
[ 'vat', {"label":"Vat","type":"text", "validation":"float"}],
[ 'vatRate', {"label":"Vat Rate","type":"text", "validation":"float"}],
[ 'issuedDate', {"label":"Issued Date","type":"datetime", "validation":""}],
[ 'dueDate', {"label":"Due Date","type":"datetime", "validation":""}],
[ 'paidDate', {"label":"Paid Date","type":"datetime", "validation":""}],
[ 'status', {"label":"Status","type":"select","source":[["Pending","Pending"],["Paid","Paid"],["Processing","Processing"],["Draft","Draft"],["Sent","Sent"],["Canceled","Canceled"]]}],
[ 'acceptPayments', {"label":"Accept Payments","type":"select","source":[["0","No"],["1","Yes"]]}],
[ 'created', {"label":"Created","type":"datetime", "validation":""}],
[ 'updated', {"label":"Updated","type":"datetime", "validation":""}],
[ 'link', {"label":"Link","type":"placeholder"}],
[ 'paymentLink', {"label":"Payment Link","type":"placeholder"}],
['items', {
label: 'Items',
type: 'datagroup',
form: [
['description', { label: 'Description', type: 'textarea', validation: '' }],
['rate', { label: 'Rate', type: 'text', validation: '' }],
['qty', { label: 'Quantity', type: 'text', validation: '' }],
['lineTotal', { label: 'Line Total', type: 'text', validation: '' }],
],
html: '<div id="#_id_#" class="panel panel-default"><div class="panel-body">#_delete_##_edit_#<span style="color:#999;font-size:13px;font-weight:bold">Date: #_date_#</span><hr/>#_note_#</div></div>',
validation: 'none',
columns: [
{
title: 'Description',
dataIndex: 'description',
key: 'description',
},
{
title: 'Rate',
dataIndex: 'rate',
key: 'rate',
},
{
title: 'Quantity',
dataIndex: 'qty',
key: 'qty',
},
{
title: 'Line Total',
dataIndex: 'lineTotal',
key: 'lineTotal',
},
],
'sort-function': function (a, b) {
const t1 = Date.parse(a.date).getTime();
const t2 = Date.parse(b.date).getTime();
return (t1 < t2);
},
'custom-validate-function': function (data) {
const res = {};
res.valid = true;
data.date = new Date().toString('d-MMM-yyyy hh:mm tt');
res.params = data;
return res;
},
}],
];
}
getTableColumns() {
return [
{
title: 'Invoice Id',
dataIndex: 'invoiceId',
sorter: true,
},
{
title: 'Description',
dataIndex: 'description',
sorter: true,
},
];
}
getTableActionButtonJsx(adapter) {
return (text, record) => (
<Space size="middle">
{adapter.hasAccess('save') && adapter.showEdit
&& (
<Tag color="green" onClick={() => modJs.edit(record.id)} style={{ cursor: 'pointer' }}>
<EditOutlined />
{` ${adapter.gt('Edit')}`}
</Tag>
)}
{adapter.hasAccess('element')
&& (
<Tag color="blue" onClick={() => modJs.viewElement(record.id)} style={{ cursor: 'pointer' }}>
<MonitorOutlined />
{` ${adapter.gt('View')}`}
</Tag>
)}
{adapter.hasAccess('delete') && adapter.showDelete
&& (
<Tag color="volcano" onClick={() => modJs.deleteRow(record.id)} style={{ cursor: 'pointer' }}>
<DeleteOutlined />
{` ${adapter.gt('Delete')}`}
</Tag>
)}
{adapter.hasAccess('save')
&& (
<Tag color="cyan" onClick={() => modJs.copyRow(record.id)} style={{ cursor: 'pointer' }}>
<CopyOutlined />
{` ${adapter.gt('Copy')}`}
</Tag>
)}
<Tag color="green" onClick={() => modJs.printInvoice(record.id)} style={{ cursor: 'pointer' }}>
<PrinterOutlined />
{` ${adapter.gt('Print')}`}
</Tag>
</Space>
);
}
printInvoice(id) {
const params = {};
params.id = id;
const reqJson = JSON.stringify(params);
const callBackData = [];
callBackData.callBackData = [];
callBackData.callBackSuccess = 'printInvoiceSuccessCallback';
callBackData.callBackFail = 'printInvoiceFailCallback';
this.customAction('printInvoice', 'extension=invoices', reqJson, callBackData);
}
printInvoiceSuccessCallback(callBackData) {
this.showMessage('Success', 'Printing Done');
this.get([]);
}
printInvoiceFailCallback(callBackData) {
this.showMessage('Error', callBackData);
}
}
module.exports ={InvoiceAdapter};

View File

@@ -361,6 +361,47 @@ gulp.task('modules-js', (done) => {
.pipe(gulp.dest('./web/dist'));
});
gulp.task('extension-js', (done) => {
let extension = process.argv.filter((item) => item.substr(0, 3) === '--x');
if (extension.length === 1) {
extension = extension[0].substr(3);
}
// map them to our stream function
return browserify({
entries: [`extensions/${extension}/web/js/index.js`],
basedir: '.',
debug: true,
cache: {},
packageCache: {},
})
.external(vendorsFlat)
.transform('babelify', {
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
],
presets: ['@babel/preset-env', '@babel/preset-react'],
extensions: ['.js', '.jsx'],
})
.transform(require('browserify-css'))
.bundle()
.pipe(source(`${extension}.js`))
.pipe(buffer())
.pipe(ifElse(!isProduction, () => sourcemaps.init({ loadMaps: true })))
.pipe(ifElse(isProduction, () => uglifyes(
{
compress: true,
mangle: {
reserved: [],
},
},
)))
.pipe(ifElse(isProduction, () => javascriptObfuscator({
compact: true,
})))
.pipe(ifElse(!isProduction, () => sourcemaps.write('./')))
.pipe(gulp.dest(`./extensions/${extension}/dist`));
});
gulp.task('watch', () => {
gulp.watch('web/admin/src/*/*.js', gulp.series('admin-js'));

View File

@@ -105,6 +105,7 @@ class UserAdapter extends AdapterBase {
if (msg == null) {
const id = $(`#${this.getTableName()}_submit #id`).val();
params.csrf = $(`#${this.getTableName()}Form`).data('csrf');
if (id != null && id !== undefined && id !== '') {
params.id = id;
this.add(params, []);

View File

@@ -453,7 +453,7 @@ class AdapterBase extends ModuleBase {
$.getJSON(this.moduleRelativeURL, {
t: this.table, a: 'ca', sa: subAction, mod: module, req: request,
}, (data) => {
if (data.status === 'SUCCESS') {
if (data.status === 'SUCCESS') {
callBackData.callBackData.push(data.data);
that.callFunction(callBackData.callBackSuccess, callBackData.callBackData);
} else {

47037
web/dist/admin-bundle.js vendored

File diff suppressed because one or more lines are too long

532
web/dist/common.js vendored

File diff suppressed because one or more lines are too long

10483
web/dist/login.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

68190
web/dist/third-party.js vendored

File diff suppressed because one or more lines are too long

91755
web/dist/vendorAntd.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

92738
web/dist/vendorAntv.js vendored

File diff suppressed because one or more lines are too long

15466
web/dist/vendorOther.js vendored

File diff suppressed because one or more lines are too long

28928
web/dist/vendorReact.js vendored

File diff suppressed because one or more lines are too long