How to create an independent module ?

Use case

The use case is quite simple - a warehouse plugin. We want to have a possibility to see how many items of a product we have in our warehouse. We also want to have the ability to track who and when received or delivered the products.

Model definition

All entities (database's table representation) used by a plugin are defined in XML files in the src/main/resources/warehouse/model/ directory. The structure of those files is described on Model Definition Overview page. The Warehouse module will define two entities: resource and the transfer. Let's start from the resource.

Resource

Resource represents a quantity of some product in the warehouse. It has the number, quantity and related product. We will define it in this file:

File src/main/reosurces/warehouse/model/resource.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- remember that this 'name' attribute determines the name of the entity,
     not the file name
-->
<model name="resource"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://schema.qcadoo.org/model"
    xsi:schemaLocation="http://schema.qcadoo.org/model http://schema.qcadoo.org/model.xsd">

    <fields>
        <!-- a number to evidence a certain resource -->
        <string name="number" required="true" unique="true" />
        <!-- an unidirectional many-to-one relation to the product entity
             from the 'basic' plugin
        -->
        <belongsTo name="product" required="true" model="product" plugin="basic" />
        <!-- amount of resource in this evidence record; we don't accept
             negative values
        -->
        <decimal name="quantity" default="0" >
            <validatesRange from="0" />
        </decimal>
    </fields>
    <hooks>
    </hooks>
    <identifier expression="#number" />
</model>

Transfer

A transfer represents describes the receive and delivery of a resource. It contains:

  • resource,
  • quantity,
  • type (incoming or outgoing),
  • status (planned or done),
  • request date,
  • the worker who filled the request
  • confirmation date/worker.

Fields that are marked as readonly will be set using a custom save action. Lets define the transfer entity in the file:

File src/main/reosurces/warehouse/model/transfer.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<!-- remember that this 'name' attribute determines the name of the entity,
     not the file name
-->
<model name="transfer"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://schema.qcadoo.org/model"
    xsi:schemaLocation="http://schema.qcadoo.org/model http://schema.qcadoo.org/model.xsd">

    <fields>
        <!-- a number to evidence a certain resource -->
        <string name="number" required="true" unique="true" />
        <!-- which from or to which resource to we transfer; the second end
             of the bidirectoral relation between a resource and transfers
        -->
        <belongsTo name="resource" model="resource" required="true" />
        <!-- how much to we transport -->
        <decimal name="quantity" required="true">
            <validatesRange from="0" />
        </decimal>
        <!-- an incoming transfer will add resource and an outgoing one will
             remove resource from the warehouse
        -->
        <enum name="type" values="01incoming,02outgoing" required="true" />
        <!-- a pending transfer did not modify the resources quantity yet, but
             it will when we close it; this field should not be set by the edit
             from but by a proper business method
        -->
        <enum name="status" values="01planned,02done" required="true" default="01planned"/>
        <!-- when we planned to close this transfer; we will have to use a
             custom validator do check if it is in the future
        -->
        <datetime name="plannedDate" />
        <!-- the worker that requested the transfer to be done and the date
             when the request was made; this will be set automatically by a hook
        -->
        <string name="requestWorker" readonly="true" />
        <datetime name="requestDate" readonly="true" />
        <!-- the worker that confirmed that the transfer was dome and the date
             when it happened; this will be set automatically by a proper
             business method
        -->
        <string name="confirmWorker" readonly="true" />
        <datetime name="confirmDate" readonly="true" />
    </fields>
    <hooks>
    </hooks>
</model>

We also have to add a hasMany field to the resource.xml.

<!-- a bidirectional many-to-one relation to the transfer entity;
             the transfer entity will have a resource field of type belongsTo,
             we will join by this field
        -->
        <hasMany name="transfers" model="transfer" joinField="resource" />

View definition

All pages used by a plugin are defined in the src/main/resources/warehouse/view directory. The structure of this file is described on View Definition Overview page. The Warehouse will define four views: a form and a for resources and for transfers. Let's start from the grid that shows resources.

Grid for resources

  • grid will contains three columns
    • number - which links to edit form
    • product's name
    • quantity
  • it will have correspondingView that points to edit form (used by linkable columns)
  • it will be paginable
  • it will be searchable and orderable using number and product's name columns
  • it will be scaled to fill all page
  • window header will be disabled
  • window will have fix height (vertical scrollbar won't appear)

We must create the resourcesList.xml file in the view directory:

src/main/reosurces/warehouse/view/resourceList.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- the 'name' attribute determines the the name of the view, not the file
     name;
     the modelName attribute describes on which entities fields
     do we concentrate here;
     the menuAccessible attribute indicates that this view will be available
     from the main menu
-->
<view name="resourcesList" modelName="resource" menuAccessible="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://schema.qcadoo.org/view"
    xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd">

    <!-- a window is always the most outer container for other components -->
    <component type="window" name="window">
        <!-- a ribbon is the big horizontal menu at the top -->
        <ribbon>
            <!-- a group contains several buttons; you can use group templates
                 to insert buttons which are hooked to default actions that
                 do simple navigation and CRUD operations
            -->
            <group template="gridNewCopyAndRemoveAction" />
        </ribbon>
        <!-- grid = table -->
        <component type="grid" name="grid" reference="grid">
            <!-- you tell this grid which columns to show using multiple
                 'column' options; in each column you can tell which
                 field to show from the entity pointed out by the modelName
                 attribute
            -->
            <option type="column" name="number" fields="number" link="true" />
            <option type="column" name="product" expression="#product['name']" link="true" />
            <option type="column" name="quantity" fields="quantity" />
            <!-- these options indicate to which view should we jump when we
                 click to edit an entity from the table or to add a new one;
                 in correspondingView we point out the view path:
                 plugin_name/view_name
                 and in the correspondingComponent we point out the components
                 reference in the view to which we want to bind the selected
                 entity
            -->
            <option type="correspondingView" value="warehouse/resourceDetails" />
            <option type="correspondingComponent" value="form" />
            <!-- this option points out which columns can be filtered -->
            <option type="searchable" value="number,product" />
            <!-- this option points out which columns change the order
                 in the grid
            -->
            <option type="orderable" value="number,product" />
            <option type="fullScreen" value="true" />
            <!-- this option indicates by which column should the grid be
                 ordered by default
            -->
            <option type="order" column="number" direction="asc"/>
        </component>
        <option type="fixedHeight" value="true" />
        <option type="header" value="false" />
    </component>
    <hooks>
    </hooks>
</view>

Ribbon defines buttons that will be displayed under the application menu. All grids should have two buttons - the first which points to the new entity form and the second which removes selected entity.

Form for resource

Now lets add a form in which we will be able to see the resources details.

  • form will contains three inputs
    • number
    • product's lookup
    • quantity - disabled, because it is set using custom action
  • expression will be used to generate window header
  • window header will be displayed

We create resourceDetails.xml file in the view directory:

src/main/reosurces/warehouse/view/resourceDetails.xml
<?xml version="1.0" encoding="UTF-8" ?>
<view name="resourceDetails" modelName="resource"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://schema.qcadoo.org/view"
    xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd">

    <component type="window" name="window">
        <ribbon>
            <group template="navigation" />
            <!-- the template for form CRUD buttons is a little different from
                 the list buttons
            -->
            <group template="formSaveCopyAndRemoveActions" />
        </ribbon>
        <!-- typical form which holds fields -->
        <component type="form" name="form" reference="form">
            <!-- input field for the number; the 'name' attribute holds the
                 name of the component, the 'field' attribute points out
                 the field of the entity from the modelName attribute
            -->
            <component type="input" name="number" field="number" />
            <!-- a lookup field in which you can select the entity -->
            <component type="lookup" name="product" field="product">
                <!-- standard grid option which will be used in the grid
                     that appears after you click the loop icon
                -->
                <option type="column" name="number" fields="number" link="true" />
                <option type="column" name="name" fields="name" link="true" />
                <option type="searchable" value="name,category" />
                <option type="orderable" value="name,category" />
                <option type="fullScreen" value="true" />
                <option type="expression" value="#name" />
                <!-- the field code indicates which field will be used to
                     shot in the lookup field after the selection was made
                -->
                <option type="fieldCode" value="number" />
            </component>
            <!-- we must use the reference if we want to do something with
                 this component in the Java code
            -->
            <component type="input" name="quantity" field="quantity" reference="resourceQuantity" />
            <option type="expression" value="#quantity + ' x ' + #number + ' (' + #product['name'] + ')'" />
            <!-- we don't want to show the forms header, the window will
                 already have one
            -->
            <option type="header" value="false" />
        </component>
    </component>
    <hooks>
    </hooks>
</view>

All forms should have one navigation button which points to the grid view and four actions buttons in their ribbon: save, save and back, cancel and delete and back.

Grid for transfers

Now a grid for transfers.

  • grid will contains four columns
    • resource's number - which links to edit form
    • quantity
    • type
    • status
  • it will have correspondingView that points to edit form (used by linkable columns)
  • it will be paginable
  • it will be searchable and orderable using resource's number, type and status columns
  • it will be scaled to fill all page
  • window header will be disabled
  • window will have fix height (vertical scrollbar won't appear)
src/main/reosurces/warehouse/view/transfersList.xml
<?xml version="1.0" encoding="UTF-8" ?>
<view name="transfersList" modelName="transfer" menuAccessible="true"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://schema.qcadoo.org/view"
      xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd">
    <component type="window" name="window">
        <ribbon>
            <group template="gridNewCopyAndRemoveAction" />
        </ribbon>
        <component type="grid" name="grid" reference="grid">
            <option type="column" name="number" fields="number" link="true" />
            <option type="column" name="resource" expression="#resource['number']" link="true" />
            <option type="column" name="type" fields="type" />
            <option type="column" name="status" fields="status" />
            <option type="column" name="quantity" fields="quantity" />
            <option type="column" name="plannedDate" fields="plannedDate" />
            <option type="correspondingView" value="warehouse/transferDetails" />
            <option type="correspondingComponent" value="form" />
            <option type="searchable" value="resource,type,status" />
            <option type="orderable" value="resource,type,status" />
            <option type="fullScreen" value="true" />
            <option type="order" column="quantity" direction="asc"/>
        </component>
        <option type="fixedHeight" value="true" />
        <option type="header" value="false" />
    </component>
</view>

Form for transfer

  • form will contains eight inputs
    • resource's lookup
    • quantity
    • type
    • status
    • requestWorked - disabled, because it is set using custom action
    • requestDate - disabled, because it is set using custom action
    • confirmWorker - disabled, because it is set using custom action
    • confirmWorker - disabled, because it is set using custom action
  • expression will be used to generate window header
  • window header will be displayed
src/main/reosurces/warehouse/view/transferDetails.xml
<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/view"
	xsi:schemaLocation="http://schema.qcadoo.org/view http://schema.qcadoo.org/view.xsd"
	name="transferDetails" modelName="transfer">
    <component type="window" name="window" reference="window">
        <ribbon>
            <group template="navigation" />
            <group template="formSaveCopyAndRemoveActions" />
        </ribbon>
        <component type="form" name="form" reference="form">
            <!-- a grid layout component gives us more control how the inputs will be positioned -->
            <component type="gridLayout" name="gridLayout" columns="2" rows="5" hasBorders="false">
                <layoutElement column="1" row="1">
                    <component type="input" name="number" field="number" />
                </layoutElement>
                <layoutElement column="1" row="2">
                    <component type="lookup" name="resource" field="resource">
                        <option type="column" name="number" fields="number" link="true" />
                        <option type="column" name="product" expression="#product['name']" link="true" />
                        <option type="searchable" value="number,name" />
                        <option type="orderable" value=" number,name" />
                        <option type="fullScreen" value="true" />
                        <option type="expression" value="#number" />
                        <option type="fieldCode" value="number" />
                    </component>
                </layoutElement>
                <layoutElement column="1" row="3">
                    <component type="input" name="quantity" field="quantity" />
                </layoutElement>
                <layoutElement column="1" row="4">
                    <component type="select" name="type" field="type" reference="type"/>
                </layoutElement>
                <layoutElement column="1" row="5">
                    <component type="calendar" name="plannedDate" field="plannedDate">
                        <option type="withTimePicker" value="true" />
                    </component>
                </layoutElement>
                <layoutElement column="2" row="1">
                    <component type="input" name="requestWorker" field="requestWorker"  />
                </layoutElement>
                <layoutElement column="2" row="2">
                    <component type="calendar" name="requestDate" field="requestDate" >
                        <option type="withTimePicker" value="true" />
                    </component>
                </layoutElement>
                <layoutElement column="2" row="3">
                    <component type="input" name="confirmWorker" field="confirmWorker" />
                </layoutElement>
                <layoutElement column="2" row="4">
                    <component type="calendar" name="confirmDate" field="confirmDate" >
                        <option type="withTimePicker" value="true" />
                    </component>
                </layoutElement>
                <layoutElement column="2" row="5">
                    <component type="select" name="status" field="status" reference="status" />
                </layoutElement>
            </component>
            <option type="expression" value="#quantity + ' x ' + #resource['number']" />
            <option type="header" value="false" />
        </component>
    </component>
    <hooks>
    </hooks>
</view>

The plugin descriptor

Now that we created our model and view XMLs we must point them out in the plugin descriptor in the file qcadoo-plugin.xml. The whole descriptor in this module will look like this:

File src/main/resources/warehouse/qcadoo-plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin plugin="warehouse" version="0.1.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.qcadoo.org/plugin"
    xmlns:model="http://schema.qcadoo.org/modules/model" xmlns:view="http://schema.qcadoo.org/modules/view"
    xmlns:menu="http://schema.qcadoo.org/modules/menu"
    xmlns:localization="http://schema.qcadoo.org/modules/localization"
    xsi:schemaLocation="
       http://schema.qcadoo.org/plugin
       http://schema.qcadoo.org/plugin.xsd
       http://schema.qcadoo.org/modules/model
       http://schema.qcadoo.org/modules/model.xsd
       http://schema.qcadoo.org/modules/view
       http://schema.qcadoo.org/modules/view.xsd
       http://schema.qcadoo.org/modules/menu
       http://schema.qcadoo.org/modules/menu.xsd
       http://schema.qcadoo.org/modules/localization
       http://schema.qcadoo.org/modules/localization.xsd">

    <information>
        <name>Warehouse Module</name>
        <vendor>
            <name>Warehouse Corp</name>
            <url>www.warehousecorp.com</url>
        </vendor>
    </information>

    <dependencies>
        <dependency>
            <plugin>basic</plugin>
        </dependency>
    </dependencies>


    <modules>
        <localization:translation path="locales" />

        <model:model model="resource" resource="model/resource.xml" />
        <model:model model="transfer" resource="model/transfer.xml" />

        <menu:menu-category name="warehouse" />

        <menu:menu-item name="resources" category="warehouse"
            view="resourcesList" />
        <menu:menu-item name="warehouseTransfers" category="warehouse"
            view="transfersList" />

        <view:view resource="view/resourcesList.xml" />
        <view:view resource="view/resourceDetails.xml" />
        <view:view resource="view/transfersList.xml"/>
        <view:view resource="view/transferDetails.xml" />

        <view:resource uri="public/**/*" />

    </modules>

</plugin>

Custom action while saving forms

Custom actions add possibility to add some business logic to our entities. Let's prepare a Java class called WarehouseService for them:

File src/main/java/com/warehousecorporation/warehouse/WarehouseService.java
package com.warehousecorporation.warehouse;

// imports

@Service
public class WarehouseService {

    // custom action definitions

}

Our first action will automatically set the transfers workers and dates. Workers will be set using the current logged user - the one who create request will be put into requestWorker, Afterwards the one who changes the status to done will be put into confirmWorker. If transfer has not been saved (id is null) we will set requests worker and date, if transfer's status is done we will set confirmation worker and date.

This action will also change quantity of the related resources, when the transfer is done.

File src/main/java/com/warehousecorporation/warehouse/WarehouseService.java
package com.warehousecorporation.warehouse;

import java.math.BigDecimal;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.model.api.Entity;
import com.qcadoo.security.api.SecurityService;
import com.qcadoo.view.api.ComponentState;
import com.qcadoo.view.api.ViewDefinitionState;

@Service
public class WarehouseService {
    
    @Autowired
    private DataDefinitionService dataDefinitionService;
    
    @Autowired
    private SecurityService securityService;

    public void setWorkersDatesAndResourceQuantity(final DataDefinition dataDefinition, final Entity transfer) {
        if (transfer.getId() == null) {
            transfer.setField("requestWorker", securityService.getCurrentUserName());
            transfer.setField("requestDate", new Date());
        }
        if ("02done".equals(transfer.getField("status"))) {
            transfer.setField("confirmWorker", securityService.getCurrentUserName());
            transfer.setField("confirmDate", new Date());

            DataDefinition resourceDataDefinition = dataDefinitionService.get("warehouse", "resource");

            Entity resource = transfer.getBelongsToField("resource");

            BigDecimal currentQuantity = (BigDecimal) resource.getField("quantity");
            BigDecimal transferQuantity = (BigDecimal) transfer.getField("quantity");
            BigDecimal newQuantity;

            if ("01incoming".equals(transfer.getField("type"))) {
                newQuantity = new BigDecimal(currentQuantity.doubleValue() - transferQuantity.doubleValue());
            } else {
                newQuantity = new BigDecimal(currentQuantity.doubleValue() + transferQuantity.doubleValue());
            }

            if (newQuantity.doubleValue() >= 0) {
                resource.setField("quantity", newQuantity);
                resourceDataDefinition.save(resource);
            }
        }
    }
}

The custom action has to be registered in src/main/resources/warehouse/model/transfer.xml in transfer model. To do this we will add the below code to the hooks node.

<onSave class="com.warehousecorporation.warehouse.WarehouseService" method="setWorkersDatesAndResourceQuantity" />

Custom validator

Custom validators add the possibility to create our own validation rules. We want to check if a resource has enough quantity for delivery transfer. Let's create validator in WarehouseService.

public boolean checkIfHasEnoughtQuantity(final DataDefinition dataDefinition, final Entity transfer) {
        if ("02done".equals(transfer.getField("status")) && "02outgoing".equals(transfer.getField("type"))) {

            Entity resource = transfer.getBelongsToField("resource");

            BigDecimal currentQuantity = (BigDecimal) resource.getField("quantity");
            BigDecimal transferQuantity = (BigDecimal) transfer.getField("quantity");

            if (transferQuantity.compareTo(currentQuantity) > 0) {
                transfer.addError(dataDefinition.getField("quantity"), "warehouse.not.enought.resource.error");
                return false;
            }
        }

        return true;
    }

This validator also has to be registered in src/main/resources/warehouse/model/transfer.xml:

<validatesWith class="com.warehousecorporation.warehouse.WarehouseService" method="checkIfHasEnoughtQuantity" />

Custom view hook

Lets add a hook which will set the initial quantity of resources (this is readonly field, so we must initial value). Lets add the following method to WarehouseService:

public void setResourceInitialQuantity(final ViewDefinitionState state) {

        ComponentState quantity = (ComponentState) state.getComponentByReference("quantity");
        if(quantity.getFieldValue() == null) {
            quantity.setFieldValue(0);
        }
    }

This action has to be registered in the view/resourceList.xml. So we have to add the below code to the end of the hooks node:

<beforeRender class="com.warehousecorporation.warehouse.WarehouseService" method="setResourceInitialQuantity" />

Localization

Localization messages are defined in src/main/resources/locales/warehouse_xx.properties files, where xx is an i18n two letter country code. Let's edit the English localization in src/main/resources/locales/warehouse_en.properties

Menu labels for the module

warehouse.menu.warehouse = Warehouse
warehouse.menu.warehouse.resources = Resources
warehouse.menu.warehouse.warehouseTransfers = Transfers
 

Messages for resource's entity

warehouse.resource.product.label = Product
warehouse.resource.product.label.focus = Type product's number
warehouse.resource.name.label = Name
warehouse.resource.number.label = Number
warehouse.resource.quantity.label = Quantity
warehouse.resource.lookupCodeVisible = Number

Messages for transfer's entity

warehouse.transfer.number.label = Number
warehouse.transfer.resource.label = Resource
warehouse.transfer.resource.label.focus = Type resource's number
warehouse.transfer.quantity.label = Quantity
warehouse.transfer.type.label = Type
warehouse.transfer.type.value.01incoming = incoming
warehouse.transfer.type.value.02outgoing = outgoing
warehouse.transfer.status.label = Status
warehouse.transfer.status.value.01planned = planned
warehouse.transfer.status.value.02done = done
warehouse.transfer.requestWorker.label = Request worker
warehouse.transfer.confirmWorker.label = Confirm worker
warehouse.transfer.plannedDate.label = Planned date
warehouse.transfer.requestDate.label = Request date
warehouse.transfer.confirmDate.label = Confirm date

Messages for resource's grid

warehouse.resourcesList.window.mainTab.grid.header = Resources:
warehouse.resourcesList.window.mainTab.grid.column.product = Product

Messages for transfer's grid

warehouse.transfersList.window.mainTab.grid.header = Transfers:
warehouse.transfersList.window.mainTab.grid.column.resource = Resource

Messages for transfer's form

warehouse.transferDetails.window.mainTab.form.headerNew = New transfer:
warehouse.transferDetails.window.mainTab.form.headerEdit = Transfer:
warehouse.transferDetails.window.mainTab.form.resource.lookup.window.grid.header = Select resource:
warehouse.transferDetails.window.mainTab.form.resource.lookup.window.grid.column.product = Product

Messages for resource's form

warehouse.resourceDetails.window.mainTab.form.headerNew = New resource:
warehouse.resourceDetails.window.mainTab.form.headerEdit = Resource:
warehouse.resourceDetails.window.mainTab.form.product.lookup.window.grid.header = Select product:

Messages for custom validator

warehouse.not.enought.resource.error = There is not enought resources in warehouse.

Change the plug-in version  

We have to changes the plugin version in the mes/mes-plugin/pom.xml file:

<properties>
    <qcadoo.version>1.1.7-SNAPSHOT</qcadoo.version>
</properties>

We also have to add dependency in mes/mes-application/pom.xml file:

<dependencies>
 <dependency>
 <groupId>com.qcadoo.mes</groupId>
 <artifactId>warehouse</artifactId>
 <version>1.1.7-SNAPSHOT</version> 
 </dependency>
 .
 .
 .
</dependencies>    

 

Installation

To add our plugin to the qcadoo MES application we have to first compile the sources. Please open the terminal, go to the plugins main directory with the pom.xml file and type:

mvn clean install

The plugin jar file will be generated under target directory. In our case the file is called warehouse-0.4.5.jar.

The next step is to install the plugin in the application. You can do it in two ways:

  1. The normal way is to open the qcadoo MES application in the browser, log in as the administrator and go the Administration (gears icon on the right) > Plug-ins page. Click Download button, choose the warehouse-0.4.3.jar file and upload it. Choose the Warehouse plugin on the plugin's grid and click Switch on. The server will be restarted and the plugin activated.
  2. The fast way is to install the plugin by copying the jar in to the directory qcadoo-bin/webapps/ROOT/WEB-INF/lib while the server is shutdown. This way is preferred for developers.

Go to the Warehouse > Resources and use your first plugin. Congratulations!

All sources for this tutorial can be found on github:

https://github.com/qcadoo/qcadoo-incubator/tree/0.4.5/warehouse

Debuging

If you installed the plugin and qcadoo MES didn't startup, you got an internal error or see a 404 page then don't panic !

You probably just made a small mistake in the modules source code. To checkout what went wrong just go to the logs directory in qcadoo-bin/logs and open the file warn.log.

You should have a Java stack-trace there with all the information you need to fix the problem.

You can also attach a debugger for a more deeper analysis. Like in any Tomcat based application just start qcadoo MES using the following command:

./catalina.sh jpda start

Just remember that on Windows you have to use the catalina.bat script. The default debuggers port will be 8000.

Add a new scenario

Lets add some new business logic. We like that every time you change the quantity of any  resource, then a new type of transfer will be created. This transfer will have the status "correction".
Let's go

Change models file

To add a new status of the transfer we have to make changes to the file transfer.xml

 <enum name="type" values="01incoming,02outgoing,03correction" required="true"/>

 and we must add the a hook to be carried out when resource resource is changed. To do this we add the following code to the hooks node of the resource.xml file in the model directory:

 <onUpdate method="createCorrectionTransfer" class="com.warehousecorporation.warehouse.WarehouseService"/>

Custom action while update forms

And now we must a the createCorrectionTransfer method to WarehouseService that implements the hook:

public void createCorrectionTransfer(final DataDefinition dataDefinition, final Entity resource){

    DataDefinition transferDataDefinition = dataDefinitionService.get("warehouse", "transfer");
    Entity correction = transferDataDefinition.create();

    correction.setField("resource", resource);
    correction.setField("quantity", resource.getField("quantity"));
    correction.setField("type", "correction");
    correction.setField("status", "closed");

    transferDataDefinition.save(correction);
}

We must also change the setWorkersDatesAndResourceQuantity method. This is very important ! if we don't do this we will end up infinite loop there an onSave resource hook modifies a transfer and an onSave transfer hook modifies a resource.

To prevent this we have to add this code at the beginning of the setWorkersDatesAndResourceQuantity method:

if("03correction".equals(transfer.getField("type"))){
     transfer.setField("requestWorker", securityService.getCurrentUserName());
     transfer.setField("requestDate", new Date());
     transfer.setField("confirmWorker", securityService.getCurrentUserName());
     transfer.setField("confirmDate", new Date());
     return;
}

Localization

 The next step is add one line for name of status correction

 warehouse.transfer.type.value.03correction = correction

Update the Warehouse plugin

We update the plugin in the same way as we install it:

  1. Compile the plugin using maven
  2. In the fast way you just have to shutdown qcadoo MES, overwrite the plugins jar in qcadoo MES with the new one you got in the plugins directory, startup qcadoo.

Exercise

  1. Add new model with name purchase:
    • product - product from basic plugin,
    • quantity - quantity of product,
    • price - price of product,
    • date - date with time.
  2. Add validator which would check if product price is > 0.
  3. Add validator which would check if product quantity is > 0.
  4. Add validator which would check if there is no product with the same name and price.
  5. Add views for that model.
  6. Add hooks which would set default system currency in field next to price (locale chosen in framework).
  7. Add hooks which would set product unit in field next to quantity - when product changes, this field also should be updated.