diff --git a/Endpoint.php b/Endpoint.php index 855391e..ac5de91 100644 --- a/Endpoint.php +++ b/Endpoint.php @@ -308,12 +308,4 @@ public function recoverAction(): array // Return the message return $message; } - - /** - * Retrieve the commission cap for the service - */ - public function capAction(): array - { - return ["status" => 200, "message" => "OK", "data" => ['cap' => $this->Auth->user()->organization()->commissionCap ?? 100]]; - } } diff --git a/VERSION b/VERSION index 3527dbc..f252462 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.0.11 +v0.0.12 diff --git a/info.cfg b/info.cfg index 1bcca85..bfb78f1 100644 --- a/info.cfg +++ b/info.cfg @@ -4,8 +4,8 @@ "base": "services", "author": "LaswitchTech", "email": "support@laswitchtech.com", - "date": "2025-08-28", - "version": "v0.0.11", + "date": "2025-08-29", + "version": "v0.0.12", "tags": "services, management, workflowhub", "description": "This plugin provides a services management interface for the WorkflowHub Core.", "repository": "https://github.com/LaswitchTech/core-plugin-services", diff --git a/library.js b/library.js index 26b235c..3a5dbc3 100644 --- a/library.js +++ b/library.js @@ -460,354 +460,341 @@ builder.add('widgets','services', class extends builder.ComponentClass { // AJAX Request $.ajax({ - url: '/api/services/cap', + url: '/api/services/fetch?id=' + id, headers: {'X-CSRF-Authorization': CSRF_KEY}, type: 'GET',dataType: 'json', error: function(xhr, status, error) { - console.error('Error fetching commission cap:', error); - reject(new Error(self._builder.Locale.get('Failed to fetch commission cap'))); + console.error('Error fetching service:', error); + reject(new Error(self._builder.Locale.get('Failed to fetch service'))); }, - success: function(commissionCap) { - commissionCap = commissionCap.cap; - - // AJAX Request - $.ajax({ - url: '/api/services/fetch?id=' + id, - headers: {'X-CSRF-Authorization': CSRF_KEY}, - type: 'GET',dataType: 'json', - error: function(xhr, status, error) { - console.error('Error fetching service:', error); - reject(new Error(self._builder.Locale.get('Failed to fetch service'))); - }, - success: function(response) { + success: function(response) { - // Set the record - const serviceRecord = response.record; + // Set the record + const serviceRecord = response.record; + const commissionCap = response.record.product.commissionCap; - // Create the Form - self._builder.Utility( - 'form', - component.body, - { - callback: { - val: function(values){ - values.commissions = []; - component.find('[data-rate]').each(function(){ - const rate = parseFloat($(this).data('rate')); - const user = $(this).data('user'); - if(!isNaN(rate) && user){ - values.commissions.push({rate: rate, user: user}); - } - }); - values.rate = (values.rate / 100).toFixed(2); - return values; + // Create the Form + self._builder.Utility( + 'form', + component.body, + { + callback: { + val: function(values){ + values.commissions = []; + component.find('[data-rate]').each(function(){ + const rate = parseFloat($(this).data('rate')).toFixed(4); + const user = $(this).data('user'); + if(!isNaN(rate) && user){ + values.commissions.push({rate: rate, user: user}); + } + }); + values.rate = (values.rate / 100).toFixed(4); + return values; + }, + submit: function(form){ + + // Show the modal spinner + modal.spinner(true); + + // Set form data + var values = form.val(); + delete values.agreement; + + // AJAX Request - Update the service (except file) + $.ajax({ + url: '/api/services/update?id='+serviceRecord.id, + headers: {'X-CSRF-Authorization': CSRF_KEY}, + type: 'POST',dataType: 'json', + data: values, + error: function(xhr, status, error) { + console.error('Error updating service:', error); }, - submit: function(form){ - - // Show the modal spinner - modal.spinner(true); - - // Set form data - var values = form.val(); - delete values.agreement; - - // AJAX Request - Update the service (except file) - $.ajax({ - url: '/api/services/update?id='+serviceRecord.id, - headers: {'X-CSRF-Authorization': CSRF_KEY}, - type: 'POST',dataType: 'json', - data: values, - error: function(xhr, status, error) { - console.error('Error updating service:', error); - }, - success: function(response) { - - // Add the new vCard to the services - update(self._services[id], response.record); - - // Hide the modal - modal.hide(); - } - }); - - // Run the file promise - form.val().agreement.then(fileData => { - - // Check if fileData is an array and has at least one file - if (!Array.isArray(fileData) || fileData.length === 0) { - console.warn('No files selected.'); - return; - } - - // Retrieve the first file - var file = fileData[0]; - - // Add some properties - file.checksum = self._builder.Helper.md5(file.content.split(',')[1]); - file.path = 'services/agreements/'; - file.isPublic = 1; - file.targetTable = self._properties.targetTable; - file.targetId = self._properties.targetId; + success: function(response) { + + // Add the new vCard to the services + update(self._services[id], response.record); + + // Hide the modal + modal.hide(); + } + }); + + // Run the file promise + form.val().agreement.then(fileData => { + + // Check if fileData is an array and has at least one file + if (!Array.isArray(fileData) || fileData.length === 0) { + console.warn('No files selected.'); + return; + } + + // Retrieve the first file + var file = fileData[0]; + + // Add some properties + file.checksum = self._builder.Helper.md5(file.content.split(',')[1]); + file.path = 'services/agreements/'; + file.isPublic = 1; + file.targetTable = self._properties.targetTable; + file.targetId = self._properties.targetId; + + // AJAX Request + $.ajax({ + url: '/api/files/upload', + headers: {'X-CSRF-Authorization': CSRF_KEY}, + type: 'POST',dataType: 'json', + data: file, + error: function(xhr, status, error) { + console.error('Error uploading agreement:', error); + }, + success: function(response) { // AJAX Request $.ajax({ - url: '/api/files/upload', + url: '/api/services/update?id='+serviceRecord.id, headers: {'X-CSRF-Authorization': CSRF_KEY}, type: 'POST',dataType: 'json', - data: file, + data: {agreement: response.record.id}, error: function(xhr, status, error) { - console.error('Error uploading agreement:', error); + console.error('Error updating service with agreement:', error); }, success: function(response) { - // AJAX Request - $.ajax({ - url: '/api/services/update?id='+serviceRecord.id, - headers: {'X-CSRF-Authorization': CSRF_KEY}, - type: 'POST',dataType: 'json', - data: {agreement: response.record.id}, - error: function(xhr, status, error) { - console.error('Error updating service with agreement:', error); - }, - success: function(response) { - - // Hide the modal - modal.hide(); - } - }); + // Hide the modal + modal.hide(); } }); - }).catch(error => { - console.error('Error reading files:', error); - }); - }, - } + } + }); + }).catch(error => { + console.error('Error reading files:', error); + }); }, - function(form,component){ + } + }, + function(form,component){ + + // Add event listener on the modal submit button + parent.content.footer.submit.click(function(e){ + e.preventDefault(); + e.stopPropagation(); + form.submit(); + }); - // Add event listener on the modal submit button - parent.content.footer.submit.click(function(e){ - e.preventDefault(); - e.stopPropagation(); - form.submit(); + // rate + form.add( + 'number', + { + name: 'rate', + label: self._builder.Locale.get('Rate'), + placeholder: self._builder.Locale.get('Enter a rate'), + required: true, + value: (serviceRecord.rate * 100).toFixed(2), + class: { + component: 'bg-gray-200 p-3 py-2 rounded-0 border-bottom', + }, + }, + function(input){ + input._component.input.attr({ + 'step':'1', + 'max':'100', + 'min':'0', }); + } + ); - // rate - form.add( - 'number', - { - name: 'rate', - label: self._builder.Locale.get('Rate'), - placeholder: self._builder.Locale.get('Enter a rate'), - required: true, - value: (serviceRecord.rate * 100).toFixed(2), - class: { - component: 'bg-gray-200 p-3 py-2 rounded-0 border-bottom', - }, - }, - function(input){ - input._component.input.attr({ - 'step':'1', - 'max':'100', - 'min':'0', - }); - } - ); + // Calculate the commission cap + function calc() { + var cap = commissionCap; + for(const [key, commission] of Object.entries(form.val().commissions ?? [])){ + cap -= parseInt((commission.rate * 100).toFixed(0)); + } + return cap; + } - // Calculate the commission cap - function calc() { - var cap = commissionCap; - for(const [key, commission] of Object.entries(form.val().commissions ?? [])){ - cap -= parseInt((commission.rate * 100).toFixed(0)); - } - return cap; - } + // Add a commission to the component + function add(commission) { + const object = $(document.createElement('div')).attr({ + 'class': 'p-3 py-2 rounded-0 border-top d-flex align-items-center', + 'data-rate': commission.rate, + 'data-user': commission.user.id, + }).insertAfter(component.controls); + object.commission = $(document.createElement('div')).text((commission.rate * 100)+'%').addClass('flex-shrink-1 me-2').appendTo(object); + object.userblock = $(document.createElement('div')).attr({ + 'class': 'flex-grow-1 d-flex align-items-center justify-content-start', + }).appendTo(object); + object.userblock.avatar = $(document.createElement('img')).attr({ + 'src': '/avatar?id='+commission.user.vcard.id, + 'class': 'avatar rounded-circle border', + 'alt': commission.user.vcard.name, + 'style': 'width: 32px; height: 32px;', + }).appendTo(object.userblock); + object.userblock.username = $(document.createElement('span')).addClass('ms-2 cursor-default').text(commission.user.username).appendTo(object.userblock); + object.delete = $(document.createElement('button')).attr({ + 'type': 'button', + 'class': 'btn btn-light', + }).html('').appendTo(object); + object.delete.hover(function(){ + $(this).removeClass('btn-light').addClass('btn-danger'); + }, function(){ + $(this).removeClass('btn-danger').addClass('btn-light'); + }).click(function(){ + object.remove(); + }); + } - // Add a commission to the component - function add(commission) { - const object = $(document.createElement('div')).attr({ - 'class': 'p-3 py-2 rounded-0 border-top d-flex align-items-center', - 'data-rate': commission.rate, - 'data-user': commission.user.id, - }).insertAfter(component.controls); - object.commission = $(document.createElement('div')).text((commission.rate * 100)+'%').addClass('flex-shrink-1 me-2').appendTo(object); - object.userblock = $(document.createElement('div')).attr({ - 'class': 'flex-grow-1 d-flex align-items-center justify-content-start', - }).appendTo(object); - object.userblock.avatar = $(document.createElement('img')).attr({ - 'src': '/avatar?id='+commission.user.vcard.id, - 'class': 'avatar rounded-circle border', - 'alt': commission.user.vcard.name, - 'style': 'width: 32px; height: 32px;', - }).appendTo(object.userblock); - object.userblock.username = $(document.createElement('span')).addClass('ms-2 cursor-default').text(commission.user.username).appendTo(object.userblock); - object.delete = $(document.createElement('button')).attr({ - 'type': 'button', - 'class': 'btn btn-light', - }).html('').appendTo(object); - object.delete.hover(function(){ - $(this).removeClass('btn-light').addClass('btn-danger'); - }, function(){ - $(this).removeClass('btn-danger').addClass('btn-light'); - }).click(function(){ - object.remove(); - }); - } + // Add a create commission button + component.controls = $(document.createElement('div')).addClass('bg-gray-200 p-3 py-2 rounded-0').appendTo(component); + component.controls.create = $(document.createElement('button')).attr({ + 'type': 'button', + 'class': 'btn btn-success w-100', + }).html(''+self._builder.Locale.get('Commission')).appendTo(component.controls); + component.controls.create.click(function(){ + + // Create the Modal + self._builder.Component( + "modal", + { + icon: "percent", + title: self._builder.Locale.get("Commission"), + color: 'success', + callback: { + load: function(component, modal){ + return new Promise((resolve, reject) => { + try { + + // Set the parent + const modalParent = component.dialog; - // Add a create commission button - component.controls = $(document.createElement('div')).addClass('bg-gray-200 p-3 py-2 rounded-0').appendTo(component); - component.controls.create = $(document.createElement('button')).attr({ - 'type': 'button', - 'class': 'btn btn-success w-100', - }).html(''+self._builder.Locale.get('Commission')).appendTo(component.controls); - component.controls.create.click(function(){ - - // Create the Modal - self._builder.Component( - "modal", - { - icon: "percent", - title: self._builder.Locale.get("Commission"), - color: 'success', - callback: { - load: function(component, modal){ - return new Promise((resolve, reject) => { - try { - - // Set the parent - const modalParent = component.dialog; - - // AJAX Request - $.ajax({ - url: '/api/auth/users', - type: 'GET',dataType: 'json', - error: function(xhr, status, error) { - console.error('Error fetching users:', error); - reject(new Error(self._builder.Locale.get('Failed to fetch users'))); - }, - success: function(response) { + // AJAX Request + $.ajax({ + url: '/api/auth/users', + type: 'GET',dataType: 'json', + error: function(xhr, status, error) { + console.error('Error fetching users:', error); + reject(new Error(self._builder.Locale.get('Failed to fetch users'))); + }, + success: function(response) { - const members = response.records; - const options = []; - for(const [id, member] of Object.entries(members)){ - options.push({id: id, text: member.username}); + const members = response.records; + const options = []; + for(const [id, member] of Object.entries(members)){ + options.push({id: id, text: member.username}); + } + + // Create the Form + self._builder.Utility( + 'form', + component.body, + { + callback: { + val: function(values){ + values.user = members[values.assignedTo]; + delete values.assignedTo; + values.rate = parseFloat((values.rate / 100).toFixed(4)); + return values; + }, + submit: function(form){ + + // Show the modal spinner + modal.spinner(true); + + // Add the commission to the form + add(form.val()); + + // Close the modal + modal.hide(); + }, } + }, + function(form,component){ + + // Add event listener on the modal submit button + modalParent.content.footer.submit.click(function(e){ + e.preventDefault(); + e.stopPropagation(); + form.submit(); + }); + + // assignedTo + form.add( + 'select2', + { + name: 'assignedTo', + label: self._builder.Locale.get('User'), + placeholder: self._builder.Locale.get('Select a user'), + class: { + component: 'bg-gray-200 p-3 py-2 rounded-0', + }, + options: options, + } + ); - // Create the Form - self._builder.Utility( - 'form', - component.body, + // rate + form.add( + 'number', { - callback: { - val: function(values){ - values.user = members[values.assignedTo]; - delete values.assignedTo; - values.rate = parseFloat((values.rate / 100).toFixed(2)); - return values; - }, - submit: function(form){ - - // Show the modal spinner - modal.spinner(true); - - // Add the commission to the form - add(form.val()); - - // Close the modal - modal.hide(); - }, - } + name: 'rate', + label: self._builder.Locale.get('Rate'), + placeholder: self._builder.Locale.get('Enter a rate'), + required: true, + value: calc(), + class: { + component: 'bg-gray-200 p-3 pb-2 pt-0 rounded-0', + }, }, - function(form,component){ - - // Add event listener on the modal submit button - modalParent.content.footer.submit.click(function(e){ - e.preventDefault(); - e.stopPropagation(); - form.submit(); + function(input){ + input._component.input.attr({ + 'step':'1', + 'max': calc(), + 'min':'0', }); - - // assignedTo - form.add( - 'select2', - { - name: 'assignedTo', - label: self._builder.Locale.get('User'), - placeholder: self._builder.Locale.get('Select a user'), - class: { - component: 'bg-gray-200 p-3 py-2 rounded-0', - }, - options: options, - } - ); - - // rate - form.add( - 'number', - { - name: 'rate', - label: self._builder.Locale.get('Rate'), - placeholder: self._builder.Locale.get('Enter a rate'), - required: true, - value: calc(), - class: { - component: 'bg-gray-200 p-3 pb-2 pt-0 rounded-0', - }, - }, - function(input){ - input._component.input.attr({ - 'step':'1', - 'max': calc(), - 'min':'0', - }); - } - ); - - // Resolve the promise - resolve(); - }, + } ); + + // Resolve the promise + resolve(); }, - }); - } catch(e) { reject(e); } + ); + }, }); - }, - }, - }, - function(modal,component){ - - // Styling - component.body.addClass('p-0'); - - // Show the modal - modal.show(); - }, - ); - }); - - // agreement - form.add( - 'file', - { - name: 'agreement', - placeholder: self._builder.Locale.get('Upload an agreement'), - class: { - component: 'bg-gray-200 p-3 py-2 rounded-0 border-top', + } catch(e) { reject(e); } + }); }, }, - ); + }, + function(modal,component){ - // Add a commissions to the component - for(const [key, commission] of Object.entries(serviceRecord.commissions ?? {})){ - add(commission); - } + // Styling + component.body.addClass('p-0'); - // Resolve the promise - resolve(); + // Show the modal + modal.show(); + }, + ); + }); + + // agreement + form.add( + 'file', + { + name: 'agreement', + placeholder: self._builder.Locale.get('Upload an agreement'), + class: { + component: 'bg-gray-200 p-3 py-2 rounded-0 border-top', + }, }, ); + + // Add a commissions to the component + for(const [key, commission] of Object.entries(serviceRecord.commissions ?? {})){ + add(commission); + } + + // Resolve the promise + resolve(); }, - }); + ); }, }); } catch(e) { reject(e); }