more doc, with lots of XXX
[TOC] | ||
# Introduction | ||
We all remember problems we had in school, stated likes: | ||
... | ... | @@ -11,9 +12,9 @@ The 5 base classes of ERP5, also called _Universal Business Model_ are shown in |
![ERP5's 5 classes](https://www.erp5.com/developer-ERP5.UBM.Picture?quality=75.0&display=small&format=png "ERP5 5 classes") | ||
Using ERP5 Universal Business Model this simple problem will be modeled using: | ||
- resource: The resource are candies | ||
- node: Alice, Bob and the Grandmother are nodes | ||
- movements: When Alice gives 2 candies to Bob, there is a movement where Alice is _source_ and Bob is _destination_. The resource of this movement is candy. | ||
- *resource:* The resource are candies | ||
- *node:* Alice, Bob and the Grandmother are nodes | ||
- *movements:* When Alice gives 2 candies to Bob, there is a movement where Alice is _source_ and Bob is _destination_. The resource of this movement is candy. | ||
In our problem, there are two movements: | ||
... | ... | @@ -62,11 +63,7 @@ return portal_simulation.getInventory( |
) | ||
``` | ||
--- | ||
## Dates | ||
# Dates | ||
We are usually interested in knowing the stock level at a certain point of time. This is rather easy, because we usually record the point in time when the movement occured. | ||
... | ... | @@ -113,7 +110,7 @@ SELECT SUM(quantity) from stock where Node='Alice' and Date <= Tuesday |
``` | ||
But Carol's inventory of candies is still 0 on Wednesday: | ||
```#python | ||
```python | ||
>>> getInventory(node=Carol, at_date=Wednesday) | ||
0 | ||
``` | ||
... | ... | @@ -134,34 +131,97 @@ SELECT SUM(quantity) from stock where Node='Carol' and Date <= Thursday |
# Complete structure of a movement | ||
quantity | ||
quantity unit | ||
resource | ||
price | ||
price currency | ||
use | ||
start_date | ||
stop_date | ||
simulation_state | ||
## Movements properties | ||
In our candy example, we abstracted many of properties that are needed on movements to represent the complexity of real business cases. | ||
A more realistic example could be ``20 kilograms of *sand* delivered from supplier to warehouse where the unit price is 10€``. | ||
In that example movement, supplier organisation is the *source*, warehouse is the *destination*. The *resource* is sand, *quantity* is 20, the *quantity unit* is Kilogram, the *price* (per unit) is 10 and the *price currency* is the Euro currency. Quantity and Price properties are set on movements and we can calculate the *total price* by multiplying the quantity by the price. In that case, total price is $20*10 = 200$. | ||
There are movements of different types that are stored in different modules, for example there are order, delivery or invoices movements. We also separate sales, purchase and internal movements. All movements have a *portal_type* property that can be for example `Purchase Order Line` or `Sale Order Line`. Portal Type comes from Zope CMF and is used to configure technical aspects such as the available view and the applicable security rules, so there is an higher level to define what is the kind of movement; this is the *use* category. | ||
Movements have a *simulation_state* which is controlled by a simulation workflow. Movements are usually part of a *delivery* on which users with the proper accreditation can pass workflow transitions to change the state. For example, one sales agent can "confirm" a sales order, that will change the simulation state of all the movements from planned to confirmed. Typically, simulation states are used for different level of certainty and have different level of planning. | ||
Movements have a *start_date* and a *stop_date* properties whose types are Zope DateTime instances. | ||
Even if in our example the total price is 200€, the "value" of this movement can be different for the supplier and for the warehouse. The supplier may want to account that the value of these 10Kg of Sand was, let's say, 110€. The *source_total_asset_price* property of a movement holds the value of this movement in the source's inventory and symetrically, the *destination_total_asset_price* the value in destination's inventory. Source asset price is always in the source's accounting currency and destination asset price always in destination's accounting currency. | ||
Movements also have an *efficiency* property to note that a percentage of the movements quantity can be lost during the movement. For example, a movement with quantity 10 and efficiency of 95% would decrease the source's inventory by 10 and increase the inventory of the destination by 9.5. This is what we call source *inventoriated quantity* and *destination inventoriated quantity*. | ||
XXX Difference from Amount and Movement. | ||
XXX Reference to movement interface. | ||
## Movements categories | ||
Previous example just described a movement going from supplier to warehouse. The *physical* location of the resource changed from supplier to the warehouse, but we want to consider other parties. For example, the *ownership* might not be to the warehouse but be the headquarters organisation. We can also states things like "Mr Smith is the main contact for questions related to this sales"; this is *administration*; or "the paiment will be made on this bank account"; this is *payment*; or "this sand will be used for the project of XXX (better resource)"; this is *project* or it that "it will be used for production operations"; this is *function*; that it will be paid using a governement grant money; this is *funding*. | ||
Movements have various source / destination categories and it seems that this list of categories depends on the kind of business being modeled. Some examples are summarized in the table below: | ||
| Category | Meaning | | ||
|--------------------|---------| | ||
|Source/Destination | What is the physical location | | ||
|Source Section/Destination Section | Who has ownership after this movement | | ||
|Source Payment/Destination Payment | Which "bank account" is used to pay / who will pay | | ||
|Source Project/Destination Project | What is the project for the source or destination | | ||
|Source Function/Destination Function | What is the "purpose" for the source or destination | | ||
|Source Administration/Destination Administration | Who is in charge of administrative tasks related to this movement | | ||
XXX vector projection | ||
# Different kind of inventories | ||
Depending of the level of certainty, we usually consider different ways of seeing the inventory. | ||
## Current Inventory | ||
## Future Inventory | ||
## Available Inventory | ||
|
||
Current inventory is what is currently in the stock. This includes all movements that have a simulation state representing the fact that this movement has already happened. | ||
To calculate current inventory, we take into account *transit* movements with special care. A movement that is in *started* state has already decreased the inventory of the source, but not yet increased the inventory of the destination. | ||
Current inventory is for example used for the actual quantities of goods in the warehouses. | ||
## Future Inventory | ||
Future inventory considers all movements that have not really happened yet, but for which we already know that their are great changes that they happen. Furure inventory takes into account *planned* or *confirmed* state and is generally used to make stocks previsions. | ||
|
||
# More axis | ||
( section, project, payment, funding ) | ||
## Available Inventory | ||
Available inventory is the inventory that can be offered to customers without risking to go out of stock. Available inventory counts incoming movements only if they really happened and count outgoing movements even if they are only planned. | ||
XXX relationship with budget | ||
# Category | ||
# Hierarchical categories | ||
Explain node_category instead of node_uid | ||
# Complete API | ||
XXX explain that the idea is to pass parameter to represent a "perimeter" to consider. | ||
## getInventory | ||
`getInventory` sums the quantity of all movements to return the quantity of resources. | ||
XXX more ? | ||
This method does not check that the provided parameters leads to results that make sense in the real world. With this method, one can request the sum of kilograms of carots + litters of gasoline. | ||
## getInventoryAssetPrice | ||
`getInventoryAssetPrice` returns the total "values" XXX of the movements. | ||
## getInventoryList | ||
`getInventoryList` can query in only one call the inventories of multiple nodes. The aggregation parameters has to be specified. For example, in the case of warehouse inventories, we want to pass `group_by_node=True` and `group_by_resource=True` to get the inventory for each node and resource. | ||
Using one `getInventoryList` is usually much more efficient than doing multiple `getInventory`/`getInventoryAssetPrice` calls. | ||
For example, this code: | ||
```python | ||
print "N1's inventory of R1 is {inventory}, value is {value}".format( | ||
inventory=getInventory(node_uid=N1.getUid(), resource=R1.getUid(), | ||
asset_price=getInventoryAssetPrice(node_uid=N1.getUid(), resource=R1.getUid())) | ||
print "N2's inventory of R1 is {inventory}, value is {value}".format( | ||
inventory=getInventory(node_uid=N2.getUid(), resource=R1.getUid(), | ||
asset_price=getInventoryAssetPrice(node_uid=N2.getUid(), resource=R1.getUid())) | ||
``` | ||
can be written more efficiently with `getInventoryList` this way: | ||
```python | ||
for brain in getInventoryList(node_uid=(N1.getUid(), N2.getUid()), | ||
resource_uid=R1.getUid(), | ||
group_by_node=True): | ||
print "{node}'s inventory of R1 is {inventory}, value is {value}".format( | ||
node=brain.node_title, | ||
inventory=brain.total_inventory, | ||
asset_price=brain.total_price) | ||
``` | ||
## getMovementHistoryList | ||
## Complete List of supported parameters | ||
... | ... | @@ -173,9 +233,55 @@ omit_input, omit_output |
omit_asset_increase, omit_asset_decrease | ||
# Inventory "documents" | ||
Whereas we calculate inventory by summing all the movements to date, there may be differences between the theorical stock in the database and the real stock on the warehouse floor. It is common business practice to make so called "inventory" (XXX check in dictionary) where people go on the floor and count how many items of each resources are physically present in each stock locations. | ||
After this, they can input the actual values in an `Inventory` document. | ||
Let's consider an example. | ||
Many incoming purchase packing lists have increased the stocks and many outgoing sales packing lists have decreased the stock. The stock levels are as displayed in this table: | ||
| Node | Resource | Quantity | | ||
| ------------- | -------- | ---------:| | ||
| N1 | R1 | 5 | | ||
| N1 | R2 | 10 | | ||
| N2 | R1 | 3 | | ||
When counting the quantities of R1 and R2 in nodes N1 and N2, we notice: | ||
| Node | Resource | Quantity | | ||
| ------------- | -------- | ---------:| | ||
| N1 | R1 | 4 | | ||
| N1 | R2 | 12 | | ||
| N2 | R1 | 3 | | ||
This is the information that will be entered in an inventory document and this inventory will be "indexed" in the stock table like this: | ||
| Node | Resource | Quantity | | ||
| ------------- | -------- | ---------:| | ||
| N1 | R1 | -1 | | ||
| N1 | R2 | 3 | | ||
Then, when we call `getInventory(node=N1, resource=R1)`, the following lines are considered: | ||
| Node | Resource | Quantity | | ||
| ------------- | -------- | ---------:| | ||
| *N1* | *R1* | *5* | | ||
| N1 | R2 | 10 | | ||
| N2 | R1 | 3 | | ||
| *N1* | *R1* | *-1* | | ||
| N1 | R2 | 3 | | ||
And the value returned by `getInventory` is 4, which matches what has been counted on the floor during inventory procedure. | ||
XXX Check terminology and maybe put below. | ||
# Flow API | ||
linear movements | ||
# Example uses | ||
## Warehouse Inventory | ||
## Time | ||
## | ||
# Tracking API | ||
XXX or another doc ? |