Version 2.3.9-1
authorJanusz Dobrowolski <janusz@frontaccounting.eu>
Mon, 25 Mar 2013 22:05:11 +0000 (23:05 +0100)
committerJanusz Dobrowolski <janusz@frontaccounting.eu>
Mon, 25 Mar 2013 22:05:11 +0000 (23:05 +0100)
README.html [new file with mode: 0644]
README.md [new file with mode: 0644]
_init/config [new file with mode: 0644]
hooks.php [new file with mode: 0644]
includes/textcart_manager.inc [new file with mode: 0644]
includes/utilities.inc [new file with mode: 0644]
inventory/adjustments.php [new file with mode: 0644]
inventory/transfers.php [new file with mode: 0644]
purchasing/po_entry_items.php [new file with mode: 0644]
sales/sales_order_entry.php [new file with mode: 0644]

diff --git a/README.html b/README.html
new file mode 100644 (file)
index 0000000..4058b30
--- /dev/null
@@ -0,0 +1,411 @@
+<h1>Module Textcart</h1>
+
+<h2>Introduction</h2>
+
+<p>This module provides a quick import/export facility between <strong>orders</strong> (or any other carts) and text via the UI.
+This allows, for example :</p>
+
+<ul>
+<li>copy/paste from a Sales Order to a Purchase Order or another Sales Order</li>
+<li>quick modification of an order</li>
+<li>modifying an Order with Excel</li>
+<li>converting email to order</li>
+<li>bulk import of big order</li>
+<li>variant, made easy via the Line Templating System (LTS)</li>
+<li>and much more</li>
+</ul>
+
+<p>Note that this module doesn't create or save orders in the database. It's just an alternative to the current UI for building an order, which then needs to be saved.</p>
+
+<h2>Why exporting to text?</h2>
+
+<p>FA is a web application, therefore you expect to be able to enter an order using the mouse via the user friendly UI, which FA provides.
+However, as nice as a mouse interface can be, entering or manipulating a text can be quicker and allows you to use whatever tool you want to modify it.
+You might want to copy a sales order to a purchase order whilst modifying the quantity to match the supplier minimum quantities or multiply all the prices by a certain factor. It's not easy with a mouse, but it's easy with Excel or any text editor.</p>
+
+<h2>How does it work?</h2>
+
+<p>Just go to any <strong>cart</strong> page (Sale Order, Purchase Order or Item Adjustment/Transfer) and select the <em>Text</em> cart next to the <em>Order</em> one.
+You should then get your order translated in text (if it's not empty).
+You can then copy/paste it into excel or anything else, modify it and re inject it in the same cart or another one. You'll then be able to see your <strong>cart</strong> in the normal UI, check it and save the order if needed.</p>
+
+<h3>Syntax overview</h3>
+
+<p>The format of a textcart is a bit complicated to explain but it's easy to write and grasp by example.
+If you edit an existing order (or add a few items in an empty one) and select the <strong>Text</strong> cart tab, you should have something like this :</p>
+
+<pre><code>  -- some lines starting with '--''
+  -- there are comments (so ignored by FA)
+  -- they are a quick reminder of the syntax
+</code></pre>
+
+<p>then the details of the cart like :</p>
+
+<pre><code>  ITEM1 + 10 $ 4.00 5.0 % | "description of item 1"
+  ITEM2 +  8 $ 5.00 0.0 % | "description of item 2"
+</code></pre>
+
+<p>etc ...</p>
+
+<p>This means you have two items in your cart, the first item being : </p>
+
+<p>ITEM1, quantity 10, price 4.00 and a discount of 5%.</p>
+
+<p><strong><em>Warning</em></strong> : the <strong>$</strong> sign here is not related to your currency, it's just a symbol meaning price.
+The format might be a bit alarming, but this one is meant to be exportable to Excel and have all the information without any ambiguity. You don't need to write everything.</p>
+
+<p>The minimum that you need to write is, the item <em>stock_code</em>.
+Then if you want (in no particular order), the quantity, the price and the discount,
+(quantities are preceded by '<strong>+</strong>', prices by '<strong>$</strong>' and discount followed by '<strong>%</strong>' ).
+Finally an optional description preceded by a '<strong>|</strong>' enclosed or not with double-quotes.</p>
+
+<ul>
+<li>fields can be separated by spaces, tabs and/or commas. </li>
+<li>order doesn't matter (for quantity, price and discount)</li>
+<li>field identifiers ('<strong>+</strong>', '<strong>$</strong>', '<strong>%</strong>') can be attached or separated to their value</li>
+<li>'<strong>+</strong>' is optional if the quantity is an integer (like <em>8</em>)</li>
+<li>'<strong>$</strong>' is optional if the price is a decimal number (like <em>5.00</em>)</li>
+<li>the default quantity is 1</li>
+</ul>
+
+<p>So, the following lines are equivalent (considering that <em>4.00</em> is the default price and <em>"description of item 1"</em> is the real item description)</p>
+
+<pre><code>  ITEM2 +  8 $ 5.00 0.0 % | "description of item 2"
+  ITEM2 +8 $5.00 0.0% | description of item 2 
+  ITEM2 +8,  $5.00, 0.0%, | description of item 2 
+  ITEM2 8 5.00 
+  ITEM2 5.00 8
+  ITEM2 8 $5
+</code></pre>
+
+<p>However </p>
+
+<pre><code>  ITEM2 8 5
+</code></pre>
+
+<p>is wrong, as <em>5</em> will be seen as a quantity, and not a price.</p>
+
+<p>(<em>Note for Excel users</em>) If you copy/paste into Excel you should have 8 columns. The separation between the '<strong>$</strong>' sign and the amount is deliberate. Without that, Excel (my version at least) would consider the column as a string, so it's easier that way.</p>
+
+<p>Lines can also contain formulas and default values through a simple but powerful line templating system (see the <em>Templating System</em> section).</p>
+
+<h2>Update Modes</h2>
+
+<p>Once, you are happy with your text (either entered directly in the text area, or copied from somewhere) you can either replace the whole order, update a part of it or just insert some items.</p>
+
+<p>Let's say your original cart contains :</p>
+
+<pre><code>ITEM-1 10
+ITEM-2 5
+</code></pre>
+
+<p>And your textcart is :</p>
+
+<pre><code>ITEM-2 10
+</code></pre>
+
+<ul>
+<li><p><strong>Replace</strong> will replace all the items with the ones in the textcart. In this case the resulting cart will be :</p>
+
+<p>ITEM-2 10</p></li>
+<li><p><strong>Insert</strong> will append the whole textcart to the original order resulting in :</p>
+
+<p>ITEM-1 10
+ITEM-2 5
+ITEM-2 10</p></li>
+</ul>
+
+<p>We have 2 lines with ITEM-2.</p>
+
+<ul>
+<li><p><strong>Update</strong> will update the lines of the original cart with the new value.</p>
+
+<p>ITEM-1 10
+ITEM-2 10</p>
+
+<p>ITEM-2 quantity has change to 10.</p></li>
+</ul>
+
+<p>Modes an also be overridden for each individual line by starting with either :  </p>
+
+<ul>
+<li>'<strong>+</strong>' insert the current line</li>
+<li>'<strong>=</strong>' update the current line</li>
+<li>'<strong>-</strong>' delete the current line.</li>
+</ul>
+
+<h2>Examples</h2>
+
+<h3>Suppress some lines from a cart</h3>
+
+<p>You want to suppress ITEM-2 from the following cart:</p>
+
+<pre><code>ITEM-1 10
+ITEM-2 5
+</code></pre>
+
+<h4>1st method</h4>
+
+<p>Delete the line ITEM2 from the text, the textcart will look like this :</p>
+
+<pre><code>ITEM-1 10
+</code></pre>
+
+<p>and press <em>Replace</em>.</p>
+
+<h4>2nd method</h4>
+
+<p>Write a text cart to delete ITEM2 with the following :</p>
+
+<pre><code>-ITEM2
+</code></pre>
+
+<p>And press <em>Insert</em>.</p>
+
+<h3>Update</h3>
+
+<p>You want to change the quantity  of ITEM-2 from the following cart to 1:</p>
+
+<p>ITEM-1 10
+   ITEM-2 5</p>
+
+<h3>1st method</h3>
+
+<p>Edit the cart just to change the quantity of ITEM-2 to 1.</p>
+
+<p>ITEM-1 10
+   ITEM-2 1</p>
+
+<p>And press <em>Replace</em>.</p>
+
+<h3>2nd method</h3>
+
+<p>Just write the line you want to change :</p>
+
+<p>ITEM-2 1</p>
+
+<p>and press <em>Update</em>.</p>
+
+<h3>Import/Export to Excel</h3>
+
+<p>If you need to do some complex modifications via Excel, just copy/paste the textcart to Excel, do your modifications, then copy/paste the desired cart from Excel to the text area and press <em>Replace</em>.</p>
+
+<h3>Sales order to purchase order</h3>
+
+<p>If you want to create a purchase order from a sales order, do the following :</p>
+
+<ul>
+<li>just copy the textcart from the sales order</li>
+<li>create a new purchase order, select the text tab from the <strong>Purchase Order</strong> page</li>
+<li>paste the sales order textcart in the textcart area</li>
+<li><p>at this point, we need to change the sales price by the purchasing price. The quickest to do so is to insert the following line (<em>Template Line</em>) at the beginning of the textcart </p>
+
+<p>:# $(@)</p></li>
+<li><p>press <em>Replace</em> and save your order.</p></li>
+</ul>
+
+<p>If you need to do fancy calculations beforehand (like matching the quantity to the minimum quantity required by the supplier), export the cart to Excel (or anything else), make the desired changes to the quantities and prices then import it to the purchase order.</p>
+
+<h3>Transfer all items from one location to another one</h3>
+
+<p>For <strong>Transfer</strong> and <strong>Adjusement</strong> items, if the cart is empty, the default textcart, rather than being empty, will be initially filled with all the items present in the specific location.</p>
+
+<h2>Line Templating System</h2>
+
+<p>For people who need to write big orders from scratch, the line templating system (LTS) allows orders to be written in a really compact way, without having to repeat what doesn't change from line to line.
+Lines sharing the same pattern can be captured to a <em>line template</em>.</p>
+
+<h3>Syntax</h3>
+
+<p>A Line template is identical to a normal line, except it starts with a ':'. It can contain constants (default values) and/or placeholders ('<strong>#</strong>','<strong>@</strong>').</p>
+
+<ul>
+<li>'<strong>#</strong>' will be replaced by the value corresponding to the same field of the current line</li>
+<li>'<strong>@</strong>' will be replaced by the default value (from the database)</li>
+</ul>
+
+<p>In the same way, '<strong>#</strong>' can be used on a normal line and will be replaced by the value of the template.</p>
+
+<h3>Examples</h3>
+
+<p>Let's say that we have the following cart</p>
+
+<pre><code>  ITEM-1 10
+  ITEM-2 10
+  ...
+  ITEM-5 10
+</code></pre>
+
+<p>We don't want to write the number 10 each time, so, let's define a template line this way :</p>
+
+<pre><code>  :# 10
+</code></pre>
+
+<p>'<strong>:</strong>' means the line is a template line.</p>
+
+<p>'<strong>#</strong>' is a placeholder for the stock_code (ignore this for the moment)</p>
+
+<p><em>10</em> is the default quantity.</p>
+
+<p>Each following line would look like the template with the '<strong>#</strong>' expanded (replaced) with the stock code.
+So, the line :</p>
+
+<pre><code>  ITEM-1
+</code></pre>
+
+<p>will be replaced by :</p>
+
+<pre><code>  ITEM-1 10
+</code></pre>
+
+<p>And the line :</p>
+
+<pre><code>  ITEM-2
+</code></pre>
+
+<p>will be replaced by :</p>
+
+<p>ITEM-2 10</p>
+
+<p>Therefore, instead of the initial cart, we can write this :</p>
+
+<pre><code>  :# 10
+  ITEM-1
+  ITEM-2
+  ...
+  ITEM-5
+</code></pre>
+
+<p>They are equivalent, but the later one is shorter.</p>
+
+<p>Placeholders can be used in any field and will be replaced by the value of the corresponding field.
+In the same way, if the stock codes follow a certain pattern (colour variant) we can use a placeholder to construct the stock_code or the description.</p>
+
+<p>Let's say we want the following order :</p>
+
+<pre><code>  T-Shirt/Blue 5
+  T-Shirt/Red 5
+  T-Shirt/Black 10
+</code></pre>
+
+<p>We can write instead :</p>
+
+<pre><code>  :T-Shirt/# 5
+  Blue
+  Red
+  Black 10
+</code></pre>
+
+<p><code>Blue</code>  will be expanded to <code>T-Shirt/Blue 5</code></p>
+
+<p><code>Red</code> to <code>T-Shirt/Red 5</code></p>
+
+<p><strong><em>Note</em></strong> The quantity for black : <em>10</em> will override the template's one.</p>
+
+<p>If we want to add the colour to the description, we just need to write the bit of the description which differs for each product.</p>
+
+<pre><code>  :T-Shirt/# | this a # t-shirt
+  Blue | blue
+  Red | red
+  Black | black
+</code></pre>
+
+<p>Is equivalent to :</p>
+
+<pre><code>  T-Shirt/Blue | this a blue t-shirt
+  T-Shirt/Red | this a red t-shirt
+  T-Shirt/Black | this a black t-shirt
+</code></pre>
+
+<h4>Placeholder in a normal line</h4>
+
+<p>You can also use a placeholder in a normal line, it will be replaced by the value in the template.</p>
+
+<p>Example :</p>
+
+<pre><code>  :Blue | blue
+  T-shirt/# | this a # shirt
+  Skirt/#   | this a # skirt
+</code></pre>
+
+<p>Is equivalent to :</p>
+
+<pre><code>  T-shirt/Blue | this a blue shirt
+  Skirt/Blue   | this a blue skirt
+</code></pre>
+
+<h3>Formula</h3>
+
+<p>Formulas between brackets will be evaluated as arithmetical expressions.</p>
+
+<p>Example, to knock down by 10% the price of every item of an existing order.</p>
+
+<p>Current cart :</p>
+
+<pre><code>  ITEM-1 10.0
+  ITEM-2 20.0
+</code></pre>
+
+<p>Desired cart :</p>
+
+<pre><code>  ITEM-1 9.0
+  ITEM-2 18.0
+</code></pre>
+
+<p>You can do :</p>
+
+<pre><code>  :# $( # * 0.90 )
+  ITEM-1 10.0
+  ITEM-2 20.0
+</code></pre>
+
+<h3>Raw line</h3>
+
+<p>A line starting with a '<strong>!</strong>' will not be expanded :</p>
+
+<p>Example</p>
+
+<pre><code>  :# $( # * 0.90 )
+  ITEM-1 10.0
+  !ITEM-2 20.0
+</code></pre>
+
+<p>will give:</p>
+
+<pre><code>  ITEM-1 9.0
+  ITEM-2 20.0
+</code></pre>
+
+<p>Note the quantity of <em>20</em> form ITEM-2.</p>
+
+<h3>Forcing value</h3>
+
+<p>In the following cart :</p>
+
+<pre><code>  :# 10
+  ITEM-1
+  ITEM-2 4
+</code></pre>
+
+<p>The line values have priority over the template values. Therefore this cart is equivalent to</p>
+
+<pre><code>  ITEM-1 10
+  ITEM-2 4
+</code></pre>
+
+<p>The <em>4</em> of the 2nd line overwrites the default value of the template.</p>
+
+<p>If you want to force <strong>Textcart</strong> to use the template value, use a <strong>constant formula</strong> (as formulas have priority over constants).</p>
+
+<p>The following cart :</p>
+
+<pre><code>  :# (10)
+  ITEM-1 
+  ITEM-2 4
+</code></pre>
+
+<p>will be equivalent to :</p>
+
+<pre><code>  ITEM-1 10
+  ITEM-2 10
+</code></pre>
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..bb8810a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,341 @@
+# Module Textcart
+## Introduction
+This module provides a quick import/export facility between **orders** (or any other carts) and text via the UI.
+This allows, for example :
+
+* copy/paste from a Sales Order to a Purchase Order or another Sales Order
+* quick modification of an order
+* modifying an Order with Excel
+* converting email to order
+* bulk import of big order
+* variant, made easy via the Line Templating System (LTS)
+* and much more
+
+Note that this module doesn't create or save orders in the database. It's just an alternative to the current UI for building an order, which then needs to be saved.
+## Why exporting to text?
+FA is a web application, therefore you expect to be able to enter an order using the mouse via the user friendly UI, which FA provides.
+However, as nice as a mouse interface can be, entering or manipulating a text can be quicker and allows you to use whatever tool you want to modify it.
+You might want to copy a sales order to a purchase order whilst modifying the quantity to match the supplier minimum quantities or multiply all the prices by a certain factor. It's not easy with a mouse, but it's easy with Excel or any text editor.
+
+## How does it work?
+Just go to any **cart** page (Sale Order, Purchase Order or Item Adjustment/Transfer) and select the *Text* cart next to the *Order* one.
+You should then get your order translated in text (if it's not empty).
+You can then copy/paste it into excel or anything else, modify it and re inject it in the same cart or another one. You'll then be able to see your **cart** in the normal UI, check it and save the order if needed.
+### Syntax overview
+The format of a textcart is a bit complicated to explain but it's easy to write and grasp by example.
+If you edit an existing order (or add a few items in an empty one) and select the **Text** cart tab, you should have something like this :
+
+      -- some lines starting with '--''
+      -- there are comments (so ignored by FA)
+      -- they are a quick reminder of the syntax
+
+then the details of the cart like :
+
+      ITEM1 + 10 $ 4.00 5.0 % | "description of item 1"
+      ITEM2 +  8 $ 5.00 0.0 % | "description of item 2"
+etc ...
+
+This means you have two items in your cart, the first item being : 
+
+ITEM1, quantity 10, price 4.00 and a discount of 5%.
+
+***Warning*** : the **$** sign here is not related to your currency, it's just a symbol meaning price.
+The format might be a bit alarming, but this one is meant to be exportable to Excel and have all the information without any ambiguity. You don't need to write everything.
+
+The minimum that you need to write is, the item *stock_code*.
+Then if you want (in no particular order), the quantity, the price and the discount,
+(quantities are preceded by '**+**', prices by '**$**' and discount followed by '**%**' ).
+Finally an optional description preceded by a '**|**' enclosed or not with double-quotes.
+
+* fields can be separated by spaces, tabs and/or commas. 
+* order doesn't matter (for quantity, price and discount)
+* field identifiers ('**+**', '**$**', '**%**') can be attached or separated to their value
+* '**+**' is optional if the quantity is an integer (like *8*)
+* '**$**' is optional if the price is a decimal number (like *5.00*)
+* the default quantity is 1
+
+So, the following lines are equivalent (considering that *4.00* is the default price and *"description of item 1"* is the real item description)
+
+      ITEM2 +  8 $ 5.00 0.0 % | "description of item 2"
+      ITEM2 +8 $5.00 0.0% | description of item 2 
+      ITEM2 +8,  $5.00, 0.0%, | description of item 2 
+      ITEM2 8 5.00 
+      ITEM2 5.00 8
+      ITEM2 8 $5 
+
+However 
+
+      ITEM2 8 5 
+
+is wrong, as *5* will be seen as a quantity, and not a price.
+
+(*Note for Excel users*) If you copy/paste into Excel you should have 8 columns. The separation between the '**$**' sign and the amount is deliberate. Without that, Excel (my version at least) would consider the column as a string, so it's easier that way.
+
+Lines can also contain formulas and default values through a simple but powerful line templating system (see the *Templating System* section).
+
+## Update Modes
+Once, you are happy with your text (either entered directly in the text area, or copied from somewhere) you can either replace the whole order, update a part of it or just insert some items.
+
+Let's say your original cart contains :
+
+    ITEM-1 10
+    ITEM-2 5
+
+And your textcart is :
+
+    ITEM-2 10
+
+* **Replace** will replace all the items with the ones in the textcart. In this case the resulting cart will be :
+
+    ITEM-2 10
+* **Insert** will append the whole textcart to the original order resulting in :
+
+
+    ITEM-1 10
+    ITEM-2 5
+    ITEM-2 10
+We have 2 lines with ITEM-2.
+
+* **Update** will update the lines of the original cart with the new value.
+
+    ITEM-1 10
+    ITEM-2 10
+   
+    ITEM-2 quantity has change to 10.
+
+Modes an also be overridden for each individual line by starting with either :  
+
+* '**+**' insert the current line
+* '**=**' update the current line
+* '**-**' delete the current line.
+  
+## Examples
+### Suppress some lines from a cart
+You want to suppress ITEM-2 from the following cart:
+
+    ITEM-1 10
+    ITEM-2 5
+
+#### 1st method
+Delete the line ITEM2 from the text, the textcart will look like this :
+
+    ITEM-1 10
+
+and press *Replace*.
+#### 2nd method
+Write a text cart to delete ITEM2 with the following :
+
+    -ITEM2
+
+And press *Insert*.
+
+### Update
+You want to change the quantity  of ITEM-2 from the following cart to 1:
+
+   ITEM-1 10
+   ITEM-2 5
+
+### 1st method
+Edit the cart just to change the quantity of ITEM-2 to 1.
+
+   ITEM-1 10
+   ITEM-2 1
+
+And press *Replace*.
+### 2nd method
+Just write the line you want to change :
+
+   ITEM-2 1
+
+and press *Update*.
+
+### Import/Export to Excel
+If you need to do some complex modifications via Excel, just copy/paste the textcart to Excel, do your modifications, then copy/paste the desired cart from Excel to the text area and press *Replace*.
+### Sales order to purchase order
+If you want to create a purchase order from a sales order, do the following :
+
+* just copy the textcart from the sales order
+* create a new purchase order, select the text tab from the **Purchase Order** page
+* paste the sales order textcart in the textcart area
+* at this point, we need to change the sales price by the purchasing price. The quickest to do so is to insert the following line (*Template Line*) at the beginning of the textcart 
+
+      :# $(@)
+
+* press *Replace* and save your order.
+
+If you need to do fancy calculations beforehand (like matching the quantity to the minimum quantity required by the supplier), export the cart to Excel (or anything else), make the desired changes to the quantities and prices then import it to the purchase order.
+
+### Transfer all items from one location to another one
+For **Transfer** and **Adjusement** items, if the cart is empty, the default textcart, rather than being empty, will be initially filled with all the items present in the specific location.
+
+## Line Templating System
+For people who need to write big orders from scratch, the line templating system (LTS) allows orders to be written in a really compact way, without having to repeat what doesn't change from line to line.
+Lines sharing the same pattern can be captured to a *line template*.
+
+### Syntax
+A Line template is identical to a normal line, except it starts with a ':'. It can contain constants (default values) and/or placeholders ('**#**','**@**').
+
+* '**#**' will be replaced by the value corresponding to the same field of the current line
+* '**@**' will be replaced by the default value (from the database)
+
+In the same way, '**#**' can be used on a normal line and will be replaced by the value of the template.
+
+### Examples
+Let's say that we have the following cart
+
+      ITEM-1 10
+      ITEM-2 10
+      ...
+      ITEM-5 10
+
+We don't want to write the number 10 each time, so, let's define a template line this way :
+
+      :# 10
+
+
+'**:**' means the line is a template line.
+
+'**#**' is a placeholder for the stock_code (ignore this for the moment)
+
+*10* is the default quantity.
+
+Each following line would look like the template with the '**#**' expanded (replaced) with the stock code.
+So, the line :
+      ITEM-1
+will be replaced by :
+
+      ITEM-1 10
+And the line :
+
+      ITEM-2
+will be replaced by :
+
+ITEM-2 10
+
+Therefore, instead of the initial cart, we can write this :
+
+      :# 10
+      ITEM-1
+      ITEM-2
+      ...
+      ITEM-5
+
+They are equivalent, but the later one is shorter.
+
+Placeholders can be used in any field and will be replaced by the value of the corresponding field.
+In the same way, if the stock codes follow a certain pattern (colour variant) we can use a placeholder to construct the stock_code or the description.
+
+Let's say we want the following order :
+
+
+      T-Shirt/Blue 5
+      T-Shirt/Red 5
+      T-Shirt/Black 10
+
+We can write instead :
+
+      :T-Shirt/# 5
+      Blue
+      Red
+      Black 10
+
+
+`Blue`  will be expanded to `T-Shirt/Blue 5`
+
+`Red` to `T-Shirt/Red 5`
+
+***Note*** The quantity for black : *10* will override the template's one.
+
+If we want to add the colour to the description, we just need to write the bit of the description which differs for each product.
+
+      :T-Shirt/# | this a # t-shirt
+      Blue | blue
+      Red | red
+      Black | black
+
+Is equivalent to :
+
+      T-Shirt/Blue | this a blue t-shirt
+      T-Shirt/Red | this a red t-shirt
+      T-Shirt/Black | this a black t-shirt
+
+
+#### Placeholder in a normal line
+You can also use a placeholder in a normal line, it will be replaced by the value in the template.
+
+Example :
+
+      :Blue | blue
+      T-shirt/# | this a # shirt
+      Skirt/#   | this a # skirt
+
+Is equivalent to :
+
+      T-shirt/Blue | this a blue shirt
+      Skirt/Blue   | this a blue skirt
+
+### Formula
+Formulas between brackets will be evaluated as arithmetical expressions.
+
+Example, to knock down by 10% the price of every item of an existing order.
+
+Current cart :
+
+      ITEM-1 10.0
+      ITEM-2 20.0
+
+Desired cart :
+
+      ITEM-1 9.0
+      ITEM-2 18.0
+
+You can do :
+
+      :# $( # * 0.90 )
+      ITEM-1 10.0
+      ITEM-2 20.0
+
+### Raw line
+A line starting with a '**!**' will not be expanded :
+
+Example
+
+      :# $( # * 0.90 )
+      ITEM-1 10.0
+      !ITEM-2 20.0
+
+will give:
+
+      ITEM-1 9.0
+      ITEM-2 20.0
+
+Note the quantity of *20* form ITEM-2.
+
+### Forcing value
+In the following cart :
+
+      :# 10
+      ITEM-1
+      ITEM-2 4
+
+The line values have priority over the template values. Therefore this cart is equivalent to
+
+      ITEM-1 10
+      ITEM-2 4
+
+The *4* of the 2nd line overwrites the default value of the template.
+
+If you want to force **Textcart** to use the template value, use a **constant formula** (as formulas have priority over constants).
+
+The following cart :
+
+      :# (10)
+      ITEM-1 
+      ITEM-2 4
+will be equivalent to :
+
+      ITEM-1 10
+      ITEM-2 10
diff --git a/_init/config b/_init/config
new file mode 100644 (file)
index 0000000..e38f1df
--- /dev/null
@@ -0,0 +1,12 @@
+Package: textcart
+Version: 2.3.9-1
+Description: Convert cart to/from text.
+ This module enables to convert carts (sales orders, purchase orders
+ adjustemen items, etc) to text allowing copy/pasting to external application.
+Name: textcart
+Author: elax <bmx007@gmail.com>
+Maintenance: elax <bmx007@gmail.com>
+Homepage: http://www.frontaccounting.com
+Type: extension
+InstallPath: modules/textcart
+
diff --git a/hooks.php b/hooks.php
new file mode 100644 (file)
index 0000000..78b7768
--- /dev/null
+++ b/hooks.php
@@ -0,0 +1,38 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+
+class hooks_textcart extends hooks {
+       var $module_name = 'textcart';
+
+       function install_options($app) {
+               $local = array(
+                       'inventory/adjustments.php',
+                       'inventory/transfers.php',
+                       'purchasing/po_entry_items.php',
+                       'sales/sales_order_entry.php'
+               );
+               // supersede some url's with provided by textcart extension
+               foreach($app->modules as $id => $mod)
+               {
+                       foreach($mod->lappfunctions as $fun=>$option)
+                               if (in_array(strtok($option->link,'?'), $local))
+                                       $app->modules[$id]->lappfunctions[$fun]->link =
+                                               'modules/textcart/'.$app->modules[$id]->lappfunctions[$fun]->link;
+                       foreach($mod->rappfunctions as $fun=>$option)
+                               if (in_array(strtok($option->link,'?'), $local))
+                                       $app->modules[$id]->rappfunctions[$fun]->link =
+                                               'modules/textcart/'.$app->modules[$id]->rappfunctions[$fun]->link;
+
+               }
+       }
+}
+?>
\ No newline at end of file
diff --git a/includes/textcart_manager.inc b/includes/textcart_manager.inc
new file mode 100644 (file)
index 0000000..f3adf5b
--- /dev/null
@@ -0,0 +1,577 @@
+<?php
+include_once($path_to_root . "/modules/textcart/includes/utilities.inc");
+define ('INSERT_MODE', 1);
+define ('UPDATE_MODE', 2);
+define ('DELETE_MODE', 3);
+define ('PARAM_REG', '+|[@#]|\(.*\)');
+define('NORMAL_LINE', 0);
+define('RAW_LINE', 1);
+define('TEMPLATE_LINE', 2);
+
+class TextCartManager {
+  var $mode_map = array (
+    '+' => INSERT_MODE
+   ,'=' => UPDATE_MODE
+    ,'-' => DELETE_MODE
+    ,'' => 0
+  );
+
+  // should be a constant but array are not allowed
+  var $line_mode_map = array (
+    '!' => RAW_LINE
+    ,':' => TEMPLATE_LINE
+    ,'' => NORMAL_LINE
+  );
+
+  var $cart_mode = null;
+
+  function handle_post_request() {
+    // Process textcart if needed
+    // we need the cart to be already loaded so we can modify it before displaying it
+    if (isset($_POST['ReplaceTextCart'])) {
+      $_POST['_tabs_sel'] = 'classic_cart'; // Don't display the textcart
+      $this->handle_textcart(true, INSERT_MODE );
+    }
+    if (isset($_POST['ModifyTextCart'])) {
+      $_POST['_tabs_sel'] = 'classic_cart'; // Don't display the textcart
+      $this->cart_mode = 'classic_cart'; // Don't display the textcart
+      $this->handle_textcart(false, INSERT_MODE);
+    }
+
+    if (isset($_POST['UpdateTextCart'])) {
+      $_POST['_tabs_sel'] = 'classic_cart'; // Don't display the textcart
+      $this->cart_mode = 'classic_cart'; // Don't display the textcart
+      $this->handle_textcart(false, UPDATE_MODE);
+    }
+
+    if (isset($_POST['CancelTextCart'])) {
+      $_POST['_tabs_sel'] = 'classic_cart'; // Don't display the textcart
+    }
+  }
+
+  function tab_display($title, $cart, $display_classic_cart) {
+    tabbed_content_start('tabs', array(
+      'classic_cart' => array(_('&Cart'), true),
+      'textcart' => array(_('&Text'), true),
+    ));
+    switch(get_post('_tabs_sel')) {
+    default:
+    case 'classic_cart':
+      $display_classic_cart($title, $cart);
+      break;
+    case 'textcart':
+      $this->display_textcart($title, $cart);
+      break;
+    }
+    tabbed_content_end();
+  }
+
+  function display_textcart($title, $cart) {
+    div_start('textcart');     
+    display_heading($title);
+    start_table(TABLESTYLE, "width=100%");
+    textarea_cells("", "textcart", $this->cart_to_text($cart), 100, 25, $title);
+    end_table();
+    //submit_center("Name", "Value", true, "title", "ajaxsubmit");
+    submit_center_first("ReplaceTextCart", "Replace");
+    submit("ModifyTextCart", "Insert");
+    submit("UpdateTextCart", "Update");
+    submit_center_last("CancelTextCart", "Cancel");
+    div_end();
+  }                                       
+
+  var $cart_key = "Items"; // key of the cart in the $POST
+  var $doc_text = "-- This is a comment -- it will be skipped and not processed 
+-- structure of a line (every fields are optional apart from stock_name)
+-- stock_name optionals fields |  description 
+-- fields could be :
+--   integer or +number : quantity
+--   float or \$nubmer  : price
+--   number%            :discount
+--   date or ^date      :date
+-- at the beginning of a line 
+-- : start a template line
+-- ! raw line (not preproceseed)
+-- before stock name
+-- + add an item
+-- - suppress the line from the cart
+-- = update the line if exists
+-- place holder               :
+-- @ : the default value of the corresponding field
+-- # : the template or line value of the corresponding field
+-- ( ...  ) : arithmetical expression between () would be evaluated
+";
+  var $use_price=true;
+  var $use_date=false;
+
+  function cart_to_text($cart) {
+    $text = $this->doc_text."
+
+
+
+"; // empty line are important
+    return $text.$this->cart_to_text_v($cart);
+  }
+
+  function item_to_text($item, $user_price_dec) {
+      $dec = get_unit_dec($item->stock_id);
+      // dodgy format excel compatible
+      return sprintf("$item->stock_id\t+ %.${dec}f\t\$ %0.${user_price_dec}f\t%0.1f %%%s\r\n"
+        ,$item->quantity
+        ,$item->price
+        ,$item->discount_percent*100
+        ,$item->item_description ? "\t| \"$item->item_description\"" : ""
+      );
+  }
+
+  function cart_to_text_v($cart)  {
+    $text = "";
+    $user_price_dec = user_price_dec();
+
+    foreach ($cart->line_items as $item) {
+      $text .= $this->item_to_text($item, $user_price_dec);
+    }
+
+    return $text;
+  }
+
+  function handle_textcart($clear_cart=false, $default_mode=null) {
+    if (!isset($_POST['textcart'])) {
+      return;
+    }
+    $cart = $_SESSION[$this->cart_key];
+    $text = $_POST['textcart'];
+    if ($clear_cart) {
+      // check that the cart can be deleted
+      foreach($cart->line_items as $line_no => $line) {
+        if($this->unmodifiable_quantity($cart, $line_no> 0)) {
+          display_error("Cannot clear current cart, because some items have been alread delivered or invoiced. Please use Update instead of Replace. ");
+          return;
+        }
+      }
+      $cart->line_items = array();
+    } // clear_items doesn't work, can't update the price if we are using it
+    $this->process_textcart($cart, $text, $default_mode);
+  }
+
+  function clear_cart($cart) {
+    $cart->line_items = array();  // clear_items doesn't work, can't update the price if we are using it
+  }
+  function process_textcart($cart, $textcart, $default_mode) {
+    $template_line = null;
+    foreach (explode("\n", $textcart) as $line) {
+      # remove comments
+      $line = preg_replace('/--.*/', "", $line);
+      # decide which type of line is
+      # ! raw line, no processing
+      # : template line
+      preg_match('/([!:]?)(.*)/', $line, $matches);
+      $line = $matches[2];
+      $line_mode = $this->line_mode_map[$matches[1]];
+
+      $attributes = $this->parse_line($line);
+
+      if ($line_mode == TEMPLATE_LINE) {
+        $template_line = $attributes ? $attributes : array();
+        continue;
+      }
+
+      if (!$attributes) continue;
+      if ($line_mode == NORMAL_LINE) {
+        $stock_code = expand_template($template_line['stock_code'], $attributes['stock_code']);
+        $mode = $attributes['mode'];
+        $quantity =  expand_template($template_line['quantity'], $attributes['quantity'], 1);
+        if($this->use_price ) {
+          $price = expand_template($template_line['price'], $attributes['price'], 
+            $this->get_default_price($cart, $stock_code));
+          //$price = round2($price, $attributes['price'], user_price_dec());
+          $discount = $attributes["discount"];
+          $discount =  expand_template($template_line['discount'], $attributes['discount'], 0);
+        }
+        else {
+          $price = 0;
+          $discount = 0;
+        }
+        if($this->use_date)  {
+          $date = expand_template($template_line['date'], $attributes['date'], 
+          $this->get_default_date($cart, $stock_code));
+        }
+        $description =  expand_template($template_line['description'], $attributes['description'], get_kit_description($stock_code));
+      }
+      else {
+        $stock_code = $attributes["stock_code"];
+        $mode = $attributes['mode'];
+        $quantity = $attributes["quantity"];
+        $price = $attributes["price"];
+        $discount = $attributes["discount"];
+        $description = $attributes["description"];
+
+      }
+
+      #echo "Stock Code : $stock_code</br><ul>";
+      #echo "<li>mode : $mode</li>";
+      #echo "<li>quantity : $quantity</li>"   ;
+      #echo "<li>price : $price</li>";
+      #echo "<li>discount : $discount</li>";
+      #echo "<li>description : $description</li>";
+      #echo "</ul>";
+
+      // Checking that product exists, to not process dodgy one
+      $kit = get_item_kit($stock_code);
+      $number = db_num_rows($kit);
+      if ($number == 0) {
+        display_error("Product '$stock_code' doesn't exist");
+        display_error("Line '$line' skipped");
+      }
+      db_free_result($kit);
+
+      if(!$mode) { $mode = $default_mode; }
+      switch ($mode) {
+      case UPDATE_MODE:
+        // we modifidy the first element, we can't use the attributes as criteria as 
+        // they are the new value
+        $line_no = $this->find_line_number($cart, $stock_code);
+        if (!isset($line_no)) { return; }
+        $this->update_cart_item($cart, $line_no, $stock_code, $quantity, $price, $discount, $date, $description);
+        break;
+      case DELETE_MODE:
+        $line_no = $this->find_line_number($cart, $stock_code, $quantity, $price, $discount, $description);
+        #echo "deleting $line_no";
+        if (!isset($line_no)) { return; }
+        $this->remove_from_cart($cart, $line_no, $stock_code);
+        break;
+      case INSERT_MODE:
+        if (!$quantity) $quantity = 1;
+        #echo "price before $price";
+        //if ($this->use_price && !$price) {
+          //$price = round2($this->get_default_price($cart, $stock_code), user_price_dec());
+        //}
+        #echo "price after $price";
+        $this->add_to_order($cart, $stock_code, $quantity, $price, $discount, $date, $description );
+      }
+    }
+  }
+
+  function remove_from_cart($cart, $line_no, $stock_code) {
+    if($this->unmodifiable_quantity($cart, $line_no) > 0)
+      display_error("'$stock_code' can not be deleted because some of it has already been dispatched or invoiced");
+    else
+        $cart->remove_from_cart($line_no);
+  }
+
+  function update_cart_item($cart, $line_no, $stock_code, $quantity, $price, $discount, $date, $description) {
+    if($this->unmodifiable_quantity($cart, $line_no) > $quantity)
+      display_error("'$stock_code' can not be updated because some of it has already been dispatched or invoiced");
+    else
+        $cart->update_cart_item($line_no, $quantity, $price, $discount, $description);
+  }
+
+  function add_to_order($cart, $stock_code, $quantity, $price, $discount, $date, $description ) {
+    if ($quantity>0)
+        add_to_order($cart, $stock_code, $quantity, $price, $discount, $description );
+    else 
+      display_error("$stock_code qty $quantity negative quantity not allowed");
+  }
+
+  function find_line_number($cart, $stock_code, $quantity=null, $price=null, $discount=null,  $description=null) {
+    foreach ($cart->line_items as $line_no => $line) {
+      if ($line->stock_id == $stock_code 
+        //&& match_criteria($line->quantity, $quantity)
+        //&& match_criteria($line->price , $price)
+        //&& match_criteria($line->discount_percent , $discount)
+        //&& match_criteria($line->item_description, $description)
+      ) {
+          return $line_no;
+
+        }
+    }
+    display_error("Can't find line details with ".join(", ",array($stock_code, $quantity, $price, $discount, $description)));
+    return null;
+  }
+  function parse_line($line) {
+    global $mode_map;
+    #echo "parsing : $line<br/>";
+    // TODO create global variable
+    $line = trim($line);
+    $stock_code = "";
+    $quantity = "";
+    $price = "";
+    $discount = "";
+    $date=null;
+    $description = "";
+
+
+    if(!$line) {
+      // empty line, skip
+      return;
+    }
+    // extract SKU and descriptions
+    if (!preg_match('/^([+\-=])?\s*([^\s,;]+)(?:([^|]*)(?:\|\s*(.*)\s*)?)?/', 
+      htmlspecialchars_decode($line), $matches)) {
+        display_error("error parsing '$line'");
+        return;
+      }
+      $mode = $this->mode_map[$matches[1]];
+      $stock_code =  $matches[2];
+      $fields_str = $matches[3];
+      $description = trim($matches[4], '" ');
+
+      # Hack to allow spaces between attribute qualifier
+      $fields_str = preg_replace('/([+$^])\s*/', '\1' , $fields_str);
+      $fields_str = preg_replace('/\s*(%)/', '\1' , $fields_str);
+    $fields = preg_split("/[\s,;]+/", $fields_str);
+
+      // TODO refactore using an array
+    foreach ($fields as $field) {
+      # quantity are integer or preceeded by a +
+      if (preg_match('/^(-?\d+'.PARAM_REG.')$/', $field, $matches)) {
+        if($quantity) {
+          display_error("quantity already set for line '$line'");
+          return;
+        };
+        $quantity = $matches[1];
+      }
+      elseif (preg_match('/^\+(-?\d*(?:\.\d+)?'.PARAM_REG.')$/', $field, $matches)) {
+        if($quantity) {
+          display_error("quantity already set for line '$line'");
+          return;
+        };
+        $quantity = $matches[1];
+      }
+      # price are float or integer preceeded by a $
+      elseif (preg_match('/^(?:(\d+\.\d+)|\$(\d+(?:\.\d+)?'.PARAM_REG.'))$/', $field, $matches)) {
+        if($price) {
+          display_error("price already set for line '$line'");
+          return;
+        };
+        $price = $matches[1] . $matches[2];//  ack to get first match or the second one
+      }
+      elseif (preg_match('/^(\d+(?:.\d+)?'.PARAM_REG.')%$/', $field, $matches)) {
+        if($discount) {
+          display_error("discount already set for line '$line'");
+          return;
+        };
+        $discount = $matches[1];
+      }
+      elseif (preg_match('/^\^?(\d+[-\/]\d+[-\/]\d+'.PARAM_REG.')$/', $field, $matches)) {
+        #echo "date = $date";
+        if($date) {
+          display_error("date already set for line '$line'");
+          return;
+        };
+        $date_php = date_parse($matches[1]);
+        if($date_php) {
+          $date=__date($date_php['year'], $date_php['month'], $date_php['day']);
+        }
+        else {
+          display_error("wrong date format for line '$line'");
+          return;
+        }
+      }
+
+      }
+    #echo "Stock Code : $stock_code</br><ul>";
+    #echo "<li>quantity : $quantity</li>"   ;
+    #echo "<li>price : $price</li>";
+    #echo "<li>discount : $discount</li>";
+    #echo "<li>date : $date</li>";
+    #echo "<li>description : $description</li>";
+    #echo "</ul>";
+
+    return array(
+      "mode" => $mode
+      ,"stock_code" => $stock_code
+      ,"quantity" => $quantity
+      ,"price" => $price
+      ,"discount" => $discount/100.0
+      ,"description" => $description
+      ,"date" => $date
+    );
+
+  }
+  function get_default_price($cart, $stock_code) {
+    return get_standard_cost($stock_code);
+  }
+
+  function get_default_date($cart, $stock_code) {
+    return add_days(Today(), 10);
+  } 
+
+  function unmodifiable_quantity($cart, $line_no) {
+    return 0;
+  }
+}
+
+class SalesTextCartManager extends TextCartManager {
+
+  function get_default_price($cart, $stock_code) {
+            return get_kit_price($stock_code, $cart->customer_currency,
+              $cart->sales_type, $cart->price_factor);
+  }
+  function unmodifiable_quantity($cart, $line_no) {
+    return $cart->line_items[$line_no]->qty_done;
+  }
+}
+
+class POTextCartManager extends TextCartManager {
+  var $cart_key = "PO";
+  var $use_date = true;
+   
+  function add_to_order($cart, $stock_code, $quantity, $price, $discount, $date, $description ) {
+    if ($quantity>0)
+        $cart->add_to_order(count($cart->line_items), $stock_code, $quantity,
+          $description, $price , '',
+                                       $date, 0, 0);
+    else 
+      display_error("$stock_code qty $quantity negative quantity not allowed");
+  }
+
+  function remove_from_cart($cart, $line_no, $stock_code) {
+        $cart->remove_from_order($line_no);
+  }
+  function update_cart_item($cart, $line_no, $stock_code, $quantity, $price, $discount, $date, $description) {
+    if($this->unmodifiable_quantity($cart, $line_no) > $quantity)
+      display_error("'$stock_code' can not be updated because some of it has already been dispatched or invoiced");
+    else
+        $cart->update_order_item($line_no, $quantity, $price, $discount, $date, $description);
+  }
+  function unmodifiable_quantity($cart, $line_no) {
+    $line = $cart->line_items[$line_no];
+    return max($line->qty_inv, $line->qty_received);
+  }
+  function get_default_price($cart, $stock_code) {
+    return get_purchase_price($_POST['supplier_id'], $stock_code);
+  }
+  function item_to_text($item, $user_price_dec) {
+      $dec = get_unit_dec($item->stock_id);
+      // dodgy format excel compatible
+      return sprintf("$item->stock_id\t+ %.${dec}f\t\$ %0.${user_price_dec}f\t^ %s%s\r\n"
+        ,$item->quantity
+        ,$item->price
+        ,$item->req_del_date
+        ,$item->item_description ? "\t| \"$item->item_description\"" : ""
+      );
+  }
+}
+
+class ItemsTextCartManager extends TextCartManager {
+  function cart_to_text_v($cart) {
+    if (count($cart->line_items) == 0) {
+      return $this->stock_to_text($this->get_location());
+    } 
+    else {
+      return parent::cart_to_text_v($cart);
+    }
+  }
+
+  function stock_to_text($location) {
+    $text = "";
+    $sql = "SELECT mv.stock_id , SUM(qty) AS quantity, description FROM ".TB_PREF."stock_moves mv
+      JOIN ".TB_PREF."stock_master USING(stock_id)
+     WHERE  mv.loc_code = '$location'
+     GROUP BY mv.stock_id
+     having quantity > 0";
+
+    $result = db_query($sql, "No transactions were returned");
+    while($move=db_fetch($result)) {
+      $dec = get_unit_dec($move['stock_id']);
+      $description = $move['description'];
+      $text.= sprintf("${move['stock_id']}\t+ %.${dec}f%s\r\n"
+        , $move['quantity']
+      ,$description ? "\t| \"$description\"" : "");
+    }
+
+    return $text;
+  }
+}
+class ItemsAdjTextCartManager extends ItemsTextCartManager {
+  var $cart_key = "adj_items";
+  var $doc_text = "-- This is a comment -- it will be skipped and not processed 
+-- structure of a line (every fields are optional apart from stock_name)
+-- stock_name optionals fields |  description 
+-- fields could be :
+--   integer or +number : quantity
+-- at the beginning of a line 
+-- : start a template line
+-- ! raw line (not preproceseed)
+-- before stock name
+-- + add an item
+-- - suppress the line from the cart
+-- = update the line if exists
+-- place holder               :
+-- @ : the default value of the corresponding field
+-- # : the template or line value of the corresponding field
+-- ( ...  ) : arithmetical expression between () would be evaluated
+
+-- *** Quantity should be negative for negative adjustement ***
+-- *** and positive for positive adjustement ***
+-- *** Wrong signed quantity will be filtered
+-- uncomment the following line to invert the sign of every lines
+-- :# +(-#)
+";
+
+  function get_location() {
+    return $_POST['StockLocation'];
+  }
+
+  function add_to_order($cart, $stock_code, $quantity, $price, $discount, $date, $description ) {
+    # filter quantity depending of the Mode
+    $mode = $_POST['Increase'];
+    if ($_POST['Increase']==1) {
+      if ($quantity>0) 
+        add_to_order($cart, $stock_code, $quantity, $price);
+      else if ($quantity<0)
+        display_error(_($stock_code)." qty $quantity <0 ");
+    }
+    else {
+      if ($quantity>0) 
+        display_error(_($stock_code)." qty $quantity >0 ");
+      else if ($quantity<0)
+        add_to_order($cart, $stock_code, -$quantity, $price);
+    }
+  }
+
+  function item_to_text($item) {
+      $dec = get_unit_dec($item->stock_id);
+      // dodgy format excel compatible
+      return sprintf("$item->stock_id\t+ %.${dec}f\t%s\r\n"
+        , $_POST['Increase']==1 ?  $item->quantity  : -$item->quantity
+        ,$item->item_description ? "\t| \"$item->item_description\"" : ""
+      );
+  }
+}
+
+class ItemsTransTextCartManager extends ItemsTextCartManager {
+  var $cart_key = "transfer_items";
+  var $doc_text = "-- This is a comment -- it will be skipped and not processed 
+-- structure of a line (every fields are optional apart from stock_name)
+-- stock_name optionals fields |  description 
+-- fields could be :
+--   integer or +number : quantity
+-- at the beginning of a line 
+-- : start a template line
+-- ! raw line (not preproceseed)
+-- before stock name
+-- + add an item
+-- - suppress the line from the cart
+-- = update the line if exists
+-- place holder               :
+-- @ : the default value of the corresponding field
+-- # : the template or line value of the corresponding field
+-- ( ...  ) : arithmetical expression between () would be evaluated
+";
+  var $use_price = false;
+  function item_to_text($item) {
+      $dec = get_unit_dec($item->stock_id);
+      // dodgy format excel compatible
+      return sprintf("$item->stock_id\t+ %.${dec}f\t%s\r\n"
+        ,$item->quantity
+        ,$item->item_description ? "\t| \"$item->item_description\"" : ""
+      );
+  }
+
+  function get_location() {
+    return $_POST['FromStockLocation'];
+  }
+}
+?>
diff --git a/includes/utilities.inc b/includes/utilities.inc
new file mode 100644 (file)
index 0000000..f6eef7e
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+// return if criteria is not set or value match it
+function match_criteria($value, $criteria) {
+ return ! ( isset($criteria) && $value == $criteria);
+}
+
+/* This function replace @ by the default value
+  # by the value in the template
+  # by the template in the value
+  and evaluate what's between ()
+
+  Order of expansion is really important.
+  Proirity are : 
+    Line value of template
+    Expression : # or () over constante
+
+  Example 
+   - | 10 => 10
+   7 | 10 => 10
+   #+3 | 10 => 13
+   7 | #+3 => 10
+   (7) | 10 => 7 (template is not constant)
+   (7) | (10) => 10 (line is not constant either)
+
+ */
+function expand_template($template, $value, $default_value=null) {
+  # replace # placeholder
+  #echo "expandind template=$template value=$value default_value=$default_value<br/>";
+  if($value) {
+    if($template) {
+        if(field_value_is_constant($value) == false) {
+          # Value needs to be evaluated (either # or ())
+          # done first as value as priority over template
+        $value = str_replace('#', $template, $value);
+  #echo "   v template=$template value=$value default_value=$default_value<br/>";
+      }
+      else if (field_value_is_constant($template) == false)  {
+        $value = str_replace('#', $value, $template);
+  #echo "   t template=$template value=$value default_value=$default_value<br/>";
+      }
+    }
+  }
+  elseif (strpos($template, '#') === false)  {
+    $value = $template;
+  #echo "    null template=$template value=$value default_value=$default_value<br/>";
+  }
+
+  if($value)  {
+  # now use default value if needed
+    $value = str_replace('@', $default_value, $value); 
+  #echo "    default template=$template value=$value default_value=$default_value<br/>";
+
+      #evaluate expression 
+      if(preg_match('/^\((.*)\)$/', $value, $matches)) {
+        // keep only arithmetical expression
+        $to_eval =  preg_replace('/[a-zA-Z]*/', "", $matches[1]);
+        #echo "eval trimmed '$matches[1]' => '$to_eval' <br/>";
+        eval("\$value=$to_eval;");
+  #echo "replaced template=$template value=$value default_value=$default_value<br/>";
+      }
+    return $value;
+  }
+  else {
+  return $default_value;
+  }
+
+}
+
+function field_value_is_constant($value) {
+  return(!preg_match('/^\(|#/', $value));
+}
+
+function get_kit_description($item_code) {
+  $db_result = get_item_kit($item_code);
+  while($props = db_fetch($db_result)) {
+    return $props['description'];
+
+  }
+
+}
+?>
diff --git a/inventory/adjustments.php b/inventory/adjustments.php
new file mode 100644 (file)
index 0000000..8f3d8f1
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+$page_security = 'SA_INVENTORYADJUSTMENT';
+$path_to_root = "../../..";
+include_once($path_to_root . "/includes/ui/items_cart.inc");
+
+include_once($path_to_root . "/includes/session.inc");
+
+include_once($path_to_root . "/includes/date_functions.inc");
+include_once($path_to_root . "/includes/data_checks.inc");
+
+include_once($path_to_root . "/inventory/includes/item_adjustments_ui.inc");
+include_once($path_to_root . "/inventory/includes/inventory_db.inc");
+include_once($path_to_root . "/modules/textcart/includes/textcart_manager.inc");
+
+$js = "";
+if ($use_popup_windows)
+       $js .= get_js_open_window(800, 500);
+if ($use_date_picker)
+       $js .= get_js_date_picker();
+page(_($help_context = "Item Adjustments Note"), false, false, "", $js);
+
+//-----------------------------------------------------------------------------------------------
+
+check_db_has_costable_items(_("There are no inventory items defined in the system which can be adjusted (Purchased or Manufactured)."));
+
+check_db_has_movement_types(_("There are no inventory movement types defined in the system. Please define at least one inventory adjustment type."));
+
+//-----------------------------------------------------------------------------------------------
+
+if (isset($_GET['AddedID'])) 
+{
+       $trans_no = $_GET['AddedID'];
+       $trans_type = ST_INVADJUST;
+
+       display_notification_centered(_("Items adjustment has been processed"));
+       display_note(get_trans_view_str($trans_type, $trans_no, _("&View this adjustment")));
+
+       display_note(get_gl_view_str($trans_type, $trans_no, _("View the GL &Postings for this Adjustment")), 1, 0);
+
+       hyperlink_no_params($_SERVER['PHP_SELF'], _("Enter &Another Adjustment"));
+
+       hyperlink_params("$path_to_root/admin/attachments.php", _("Add an Attachment"), "filterType=$trans_type&trans_no=$trans_no");
+
+       display_footer_exit();
+}
+//--------------------------------------------------------------------------------------------------
+
+function line_start_focus() {
+  global       $Ajax;
+
+  $Ajax->activate('items_table');
+  set_focus('_stock_id_edit');
+}
+//-----------------------------------------------------------------------------------------------
+
+function handle_new_order()
+{
+       if (isset($_SESSION['adj_items']))
+       {
+               $_SESSION['adj_items']->clear_items();
+               unset ($_SESSION['adj_items']);
+       }
+
+    //session_register("adj_items");
+
+    $_SESSION['adj_items'] = new items_cart(ST_INVADJUST);
+       $_POST['AdjDate'] = new_doc_date();
+       if (!is_date_in_fiscalyear($_POST['AdjDate']))
+               $_POST['AdjDate'] = end_fiscalyear();
+       $_SESSION['adj_items']->tran_date = $_POST['AdjDate'];  
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function can_process()
+{
+       global $Refs;
+
+       $adj = &$_SESSION['adj_items'];
+
+       if (count($adj->line_items) == 0)       {
+               display_error(_("You must enter at least one non empty item line."));
+               set_focus('stock_id');
+               return false;
+       }
+       if (!$Refs->is_valid($_POST['ref'])) 
+       {
+               display_error( _("You must enter a reference."));
+               set_focus('ref');
+               return false;
+       }
+
+       if (!is_new_reference($_POST['ref'], ST_INVADJUST)) 
+       {
+               display_error( _("The entered reference is already in use."));
+               set_focus('ref');
+               return false;
+       }
+
+       if (!is_date($_POST['AdjDate'])) 
+       {
+               display_error(_("The entered date for the adjustment is invalid."));
+               set_focus('AdjDate');
+               return false;
+       } 
+       elseif (!is_date_in_fiscalyear($_POST['AdjDate'])) 
+       {
+               display_error(_("The entered date is not in fiscal year."));
+               set_focus('AdjDate');
+               return false;
+       } else {
+               $failed_item = $adj->check_qoh($_POST['StockLocation'], $_POST['AdjDate'], !$_POST['Increase']);
+               if ($failed_item >= 0) 
+               {
+                       $line = $adj->line_items[$failed_item];
+               display_error(_("The adjustment cannot be processed because an adjustment item would cause a negative inventory balance :") .
+                       " " . $line->stock_id . " - " .  $line->item_description);
+                       $_POST['Edit'.$failed_item] = 1; // enter edit mode
+                       unset($_POST['Process']);
+               return false;
+               }
+       }
+       return true;
+}
+
+//-------------------------------------------------------------------------------
+
+if (isset($_POST['Process']) && can_process()){
+
+       $trans_no = add_stock_adjustment($_SESSION['adj_items']->line_items,
+               $_POST['StockLocation'], $_POST['AdjDate'],     $_POST['type'], $_POST['Increase'],
+               $_POST['ref'], $_POST['memo_']);
+       new_doc_date($_POST['AdjDate']);
+       $_SESSION['adj_items']->clear_items();
+       unset($_SESSION['adj_items']);
+
+       meta_forward($_SERVER['PHP_SELF'], "AddedID=$trans_no");
+} /*end of process credit note */
+
+//-----------------------------------------------------------------------------------------------
+
+function check_item_data()
+{
+       if (!check_num('qty',0))
+       {
+               display_error(_("The quantity entered is negative or invalid."));
+               set_focus('qty');
+               return false;
+       }
+
+       if (!check_num('std_cost', 0))
+       {
+               display_error(_("The entered standard cost is negative or invalid."));
+               set_focus('std_cost');
+               return false;
+       }
+
+       return true;
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_update_item()
+{
+    if($_POST['UpdateItem'] != "" && check_item_data())
+    {
+               $id = $_POST['LineNo'];
+       $_SESSION['adj_items']->update_cart_item($id, input_num('qty'), 
+                       input_num('std_cost'));
+    }
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_delete_item($id)
+{
+       $_SESSION['adj_items']->remove_from_cart($id);
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_new_item()
+{
+       if (!check_item_data())
+               return;
+
+       add_to_order($_SESSION['adj_items'], $_POST['stock_id'], 
+         input_num('qty'), input_num('std_cost'));
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+$id = find_submit('Delete');
+if ($id != -1)
+       handle_delete_item($id);
+
+if (isset($_POST['AddItem']))
+       handle_new_item();
+
+if (isset($_POST['UpdateItem']))
+       handle_update_item();
+
+if (isset($_POST['CancelItemChanges'])) {
+       line_start_focus();
+}
+//-----------------------------------------------------------------------------------------------
+
+if (isset($_GET['NewAdjustment']) || !isset($_SESSION['adj_items']))
+{
+       handle_new_order();
+}
+
+//-----------------------------------------------------------------------------------------------
+$textcart_mgr = new ItemsAdjTextCartManager();
+$textcart_mgr->handle_post_request();
+  function display_order_in_tab($title, $cart) {
+    display_adjustment_items($title, $cart);
+  }
+
+start_form();
+
+display_order_header($_SESSION['adj_items']);
+
+start_outer_table(TABLESTYLE, "width=70%", 10);
+
+$textcart_mgr->tab_display(_("Adjustment Items"), $_SESSION['adj_items'], "display_order_in_tab");
+adjustment_options_controls();
+
+end_outer_table(1, false);
+
+submit_center_first('Update', _("Update"), '', null);
+submit_center_last('Process', _("Process Adjustment"), '', 'default');
+
+end_form();
+end_page();
+
+?>
diff --git a/inventory/transfers.php b/inventory/transfers.php
new file mode 100644 (file)
index 0000000..14e9e2f
--- /dev/null
@@ -0,0 +1,252 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+$page_security = 'SA_LOCATIONTRANSFER';
+$path_to_root = "../../..";
+include_once($path_to_root . "/includes/ui/items_cart.inc");
+
+include_once($path_to_root . "/includes/session.inc");
+
+include_once($path_to_root . "/includes/date_functions.inc");
+include_once($path_to_root . "/includes/data_checks.inc");
+
+include_once($path_to_root . "/inventory/includes/stock_transfers_ui.inc");
+include_once($path_to_root . "/inventory/includes/inventory_db.inc");
+include_once($path_to_root . "/modules/textcart/includes/textcart_manager.inc");
+
+$js = "";
+if ($use_popup_windows)
+       $js .= get_js_open_window(800, 500);
+if ($use_date_picker)
+       $js .= get_js_date_picker();
+page(_($help_context = "Inventory Location Transfers"), false, false, "", $js);
+
+//-----------------------------------------------------------------------------------------------
+
+check_db_has_costable_items(_("There are no inventory items defined in the system (Purchased or manufactured items)."));
+
+check_db_has_movement_types(_("There are no inventory movement types defined in the system. Please define at least one inventory adjustment type."));
+
+//-----------------------------------------------------------------------------------------------
+
+if (isset($_GET['AddedID'])) 
+{
+       $trans_no = $_GET['AddedID'];
+       $trans_type = ST_LOCTRANSFER;
+
+       display_notification_centered(_("Inventory transfer has been processed"));
+       display_note(get_trans_view_str($trans_type, $trans_no, _("&View this transfer")));
+
+       hyperlink_no_params($_SERVER['PHP_SELF'], _("Enter &Another Inventory Transfer"));
+
+       display_footer_exit();
+}
+//--------------------------------------------------------------------------------------------------
+
+function line_start_focus() {
+  global       $Ajax;
+
+  $Ajax->activate('items_table');
+  set_focus('_stock_id_edit');
+}
+//-----------------------------------------------------------------------------------------------
+
+function handle_new_order()
+{
+       if (isset($_SESSION['transfer_items']))
+       {
+               $_SESSION['transfer_items']->clear_items();
+               unset ($_SESSION['transfer_items']);
+       }
+
+    //session_register("transfer_items");
+
+       $_SESSION['transfer_items'] = new items_cart(ST_LOCTRANSFER);
+       $_POST['AdjDate'] = new_doc_date();
+       if (!is_date_in_fiscalyear($_POST['AdjDate']))
+               $_POST['AdjDate'] = end_fiscalyear();
+       $_SESSION['transfer_items']->tran_date = $_POST['AdjDate'];     
+}
+
+//-----------------------------------------------------------------------------------------------
+
+if (isset($_POST['Process']))
+{
+       global $Refs;
+
+       $tr = &$_SESSION['transfer_items'];
+       $input_error = 0;
+
+       if (count($tr->line_items) == 0)        {
+               display_error(_("You must enter at least one non empty item line."));
+               set_focus('stock_id');
+               return false;
+       }
+       if (!$Refs->is_valid($_POST['ref'])) 
+       {
+               display_error(_("You must enter a reference."));
+               set_focus('ref');
+               $input_error = 1;
+       } 
+       elseif (!is_new_reference($_POST['ref'], ST_LOCTRANSFER)) 
+       {
+               display_error(_("The entered reference is already in use."));
+               set_focus('ref');
+               $input_error = 1;
+       } 
+       elseif (!is_date($_POST['AdjDate'])) 
+       {
+               display_error(_("The entered date for the adjustment is invalid."));
+               set_focus('AdjDate');
+               $input_error = 1;
+       } 
+       elseif (!is_date_in_fiscalyear($_POST['AdjDate'])) 
+       {
+               display_error(_("The entered date is not in fiscal year."));
+               set_focus('AdjDate');
+               $input_error = 1;
+       } 
+       elseif ($_POST['FromStockLocation'] == $_POST['ToStockLocation'])
+       {
+               display_error(_("The locations to transfer from and to must be different."));
+               set_focus('FromStockLocation');
+               $input_error = 1;
+       } 
+       else 
+       {
+               $failed_item = $tr->check_qoh($_POST['FromStockLocation'], $_POST['AdjDate'], true);
+               if ($failed_item >= 0) 
+               {
+                       $line = $tr->line_items[$failed_item];
+               display_error(_("The quantity entered is greater than the available quantity for this item at the source location :") .
+                       " " . $line->stock_id . " - " .  $line->item_description);
+               echo "<br>";
+                       $_POST['Edit'.$failed_item] = 1; // enter edit mode
+                       $input_error = 1;
+               }
+       }
+
+       if ($input_error == 1)
+               unset($_POST['Process']);
+}
+
+//-------------------------------------------------------------------------------
+
+if (isset($_POST['Process']))
+{
+
+       $trans_no = add_stock_transfer($_SESSION['transfer_items']->line_items,
+               $_POST['FromStockLocation'], $_POST['ToStockLocation'],
+               $_POST['AdjDate'], $_POST['type'], $_POST['ref'], $_POST['memo_']);
+       new_doc_date($_POST['AdjDate']);
+       $_SESSION['transfer_items']->clear_items();
+       unset($_SESSION['transfer_items']);
+
+       meta_forward($_SERVER['PHP_SELF'], "AddedID=$trans_no");
+} /*end of process credit note */
+
+//-----------------------------------------------------------------------------------------------
+
+function check_item_data()
+{
+       if (!check_num('qty', 0))
+       {
+               display_error(_("The quantity entered must be a positive number."));
+               set_focus('qty');
+               return false;
+       }
+       return true;
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_update_item()
+{
+    if($_POST['UpdateItem'] != "" && check_item_data())
+    {
+               $id = $_POST['LineNo'];
+       if (!isset($_POST['std_cost']))
+               $_POST['std_cost'] = $_SESSION['transfer_items']->line_items[$id]->standard_cost;
+       $_SESSION['transfer_items']->update_cart_item($id, input_num('qty'), $_POST['std_cost']);
+    }
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_delete_item($id)
+{
+       $_SESSION['transfer_items']->remove_from_cart($id);
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+
+function handle_new_item()
+{
+       if (!check_item_data())
+               return;
+       if (!isset($_POST['std_cost']))
+               $_POST['std_cost'] = 0;
+       add_to_order($_SESSION['transfer_items'], $_POST['stock_id'], input_num('qty'), $_POST['std_cost']);
+       line_start_focus();
+}
+
+//-----------------------------------------------------------------------------------------------
+$id = find_submit('Delete');
+if ($id != -1)
+       handle_delete_item($id);
+       
+if (isset($_POST['AddItem']))
+       handle_new_item();
+
+if (isset($_POST['UpdateItem']))
+       handle_update_item();
+
+if (isset($_POST['CancelItemChanges'])) {
+       line_start_focus();
+}
+//-----------------------------------------------------------------------------------------------
+
+if (isset($_GET['NewTransfer']) || !isset($_SESSION['transfer_items']))
+{
+       handle_new_order();
+}
+
+
+//-----------------------------------------------------------------------------------------------
+$textcart_mgr = new ItemsTransTextCartManager();
+$textcart_mgr->handle_post_request();
+function display_order_in_tab ($title, $cart) {
+  display_transfer_items($title, $cart);
+}
+
+start_form();
+
+display_order_header($_SESSION['transfer_items']);
+
+start_table(TABLESTYLE, "width=70%", 10);
+start_row();
+echo "<td>";
+$textcart_mgr->tab_display(_("Items"), $_SESSION['transfer_items'], "display_order_in_tab");
+
+transfer_options_controls();
+echo "</td>";
+end_row();
+end_table(1);
+
+submit_center_first('Update', _("Update"), '', null);
+submit_center_last('Process', _("Process Transfer"), '',  'default');
+
+end_form();
+end_page();
+
+?>
diff --git a/purchasing/po_entry_items.php b/purchasing/po_entry_items.php
new file mode 100644 (file)
index 0000000..e0c4e77
--- /dev/null
@@ -0,0 +1,557 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+$path_to_root = "../../..";
+$page_security = 'SA_PURCHASEORDER';
+include_once($path_to_root . "/purchasing/includes/po_class.inc");
+include_once($path_to_root . "/includes/session.inc");
+include_once($path_to_root . "/purchasing/includes/purchasing_ui.inc");
+include_once($path_to_root . "/purchasing/includes/db/suppliers_db.inc");
+include_once($path_to_root . "/reporting/includes/reporting.inc");
+include_once($path_to_root . "/modules/textcart/includes/textcart_manager.inc");
+
+set_page_security( @$_SESSION['PO']->trans_type,
+       array(  ST_PURCHORDER => 'SA_PURCHASEORDER',
+                       ST_SUPPRECEIVE => 'SA_GRN',
+                       ST_SUPPINVOICE => 'SA_SUPPLIERINVOICE'),
+       array(  'NewOrder' => 'SA_PURCHASEORDER',
+                       'ModifyOrderNumber' => 'SA_PURCHASEORDER',
+                       'NewGRN' => 'SA_GRN',
+                       'NewInvoice' => 'SA_SUPPLIERINVOICE')
+);
+
+$js = '';
+if ($use_popup_windows)
+       $js .= get_js_open_window(900, 500);
+if ($use_date_picker)
+       $js .= get_js_date_picker();
+
+if (isset($_GET['ModifyOrderNumber']) && is_numeric($_GET['ModifyOrderNumber'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Modify Purchase Order #") . $_GET['ModifyOrderNumber'];
+       create_new_po(ST_PURCHORDER, $_GET['ModifyOrderNumber']);
+       copy_from_cart();
+} elseif (isset($_GET['NewOrder'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Purchase Order Entry");
+       create_new_po(ST_PURCHORDER, 0);
+       copy_from_cart();
+} elseif (isset($_GET['NewGRN'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Direct GRN Entry");
+       create_new_po(ST_SUPPRECEIVE, 0);
+       copy_from_cart();
+} elseif (isset($_GET['NewInvoice'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Direct Purchase Invoice Entry");
+       create_new_po(ST_SUPPINVOICE, 0);
+       copy_from_cart();
+}
+
+page($_SESSION['page_title'], false, false, "", $js);
+
+//---------------------------------------------------------------------------------------------------
+
+check_db_has_suppliers(_("There are no suppliers defined in the system."));
+
+check_db_has_purchasable_items(_("There are no purchasable inventory items defined in the system."));
+
+//---------------------------------------------------------------------------------------------------------------
+
+if (isset($_GET['AddedID'])) 
+{
+       $order_no = $_GET['AddedID'];
+       $trans_type = ST_PURCHORDER;    
+
+       if (!isset($_GET['Updated']))
+               display_notification_centered(_("Purchase Order has been entered"));
+       else
+               display_notification_centered(_("Purchase Order has been updated") . " #$order_no");
+       display_note(get_trans_view_str($trans_type, $order_no, _("&View this order")), 0, 1);
+
+       display_note(print_document_link($order_no, _("&Print This Order"), true, $trans_type), 0, 1);
+
+       display_note(print_document_link($order_no, _("&Email This Order"), true, $trans_type, false, "printlink", "", 1));
+
+       hyperlink_params($path_to_root . "/purchasing/po_receive_items.php", _("&Receive Items on this Purchase Order"), "PONumber=$order_no");
+
+       hyperlink_params($_SERVER['PHP_SELF'], _("Enter &Another Purchase Order"), "NewOrder=yes");
+       
+       hyperlink_no_params($path_to_root."/purchasing/inquiry/po_search.php", _("Select An &Outstanding Purchase Order"));
+       
+       display_footer_exit();  
+
+} elseif (isset($_GET['AddedGRN'])) {
+
+       $trans_no = $_GET['AddedGRN'];
+       $trans_type = ST_SUPPRECEIVE;
+
+       display_notification_centered(_("Direct GRN has been entered"));
+
+       display_note(get_trans_view_str($trans_type, $trans_no, _("&View this GRN")), 0);
+
+    $clearing_act = get_company_pref('grn_clearing_act');
+       if ($clearing_act)      
+               display_note(get_gl_view_str($trans_type, $trans_no, _("View the GL Journal Entries for this Delivery")), 1);
+// not yet
+//     display_note(print_document_link($trans_no, _("&Print This GRN"), true, $trans_type), 0, 1);
+
+       hyperlink_params("$path_to_root/purchasing/supplier_invoice.php",
+               _("Entry purchase &invoice for this receival"), "New=1");
+
+       hyperlink_params("$path_to_root/admin/attachments.php", _("Add an Attachment"), 
+               "filterType=$trans_type&trans_no=$trans_no");
+
+       hyperlink_params($_SERVER['PHP_SELF'], _("Enter &Another GRN"), "NewGRN=Yes");
+       
+       display_footer_exit();  
+
+} elseif (isset($_GET['AddedPI'])) {
+
+       $trans_no = $_GET['AddedPI'];
+       $trans_type = ST_SUPPINVOICE;
+
+       display_notification_centered(_("Direct Purchase Invoice has been entered"));
+
+       display_note(get_trans_view_str($trans_type, $trans_no, _("&View this Invoice")), 0);
+
+// not yet
+//     display_note(print_document_link($trans_no, _("&Print This Invoice"), true, $trans_type), 0, 1);
+
+       display_note(get_gl_view_str($trans_type, $trans_no, _("View the GL Journal Entries for this Invoice")), 1);
+
+       hyperlink_params("$path_to_root/purchasing/supplier_payment.php", _("Entry supplier &payment for this invoice"),
+               "PInvoice=".$trans_no);
+
+       hyperlink_params("$path_to_root/admin/attachments.php", _("Add an Attachment"), 
+               "filterType=$trans_type&trans_no=$trans_no");
+
+       hyperlink_params($_SERVER['PHP_SELF'], _("Enter &Another Direct Invoice"), "NewInvoice=Yes");
+       
+       display_footer_exit();  
+}
+//--------------------------------------------------------------------------------------------------
+function line_start_focus() {
+  global       $Ajax;
+
+  $Ajax->activate('items_table');
+  set_focus('_stock_id_edit');
+}
+//--------------------------------------------------------------------------------------------------
+
+function unset_form_variables() {
+       unset($_POST['stock_id']);
+    unset($_POST['qty']);
+    unset($_POST['price']);
+    unset($_POST['req_del_date']);
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function handle_delete_item($line_no)
+{
+       if($_SESSION['PO']->some_already_received($line_no) == 0)
+       {
+               $_SESSION['PO']->remove_from_order($line_no);
+               unset_form_variables();
+       } 
+       else 
+       {
+               display_error(_("This item cannot be deleted because some of it has already been received."));
+       }       
+    line_start_focus();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function handle_cancel_po()
+{
+       global $path_to_root;
+       
+       //need to check that not already dispatched or invoiced by the supplier
+       if(($_SESSION['PO']->order_no != 0) && 
+               $_SESSION['PO']->any_already_received() == 1)
+       {
+               display_error(_("This order cannot be cancelled because some of it has already been received.") 
+                       . "<br>" . _("The line item quantities may be modified to quantities more than already received. prices cannot be altered for lines that have already been received and quantities cannot be reduced below the quantity already received."));
+               return;
+       }
+       
+       if($_SESSION['PO']->order_no != 0)
+       {
+               delete_po($_SESSION['PO']->order_no);
+       } else {
+               unset($_SESSION['PO']);
+               meta_forward($path_to_root.'/index.php','application=AP');
+       }
+
+       $_SESSION['PO']->clear_items();
+       $_SESSION['PO'] = new purch_order;
+
+       display_notification(_("This purchase order has been cancelled."));
+
+       hyperlink_params($path_to_root . "/purchasing/po_entry_items.php", _("Enter a new purchase order"), "NewOrder=Yes");
+       echo "<br>";
+
+       end_page();
+       exit;
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function check_data()
+{
+       if(!get_post('stock_id_text', true)) {
+               display_error( _("Item description cannot be empty."));
+               set_focus('stock_id_edit');
+               return false;
+       }
+
+       $dec = get_qty_dec($_POST['stock_id']);
+       $min = 1 / pow(10, $dec);
+    if (!check_num('qty',$min))
+    {
+       $min = number_format2($min, $dec);
+               display_error(_("The quantity of the order item must be numeric and not less than ").$min);
+               set_focus('qty');
+               return false;
+    }
+
+    if (!check_num('price', 0))
+    {
+               display_error(_("The price entered must be numeric and not less than zero."));
+               set_focus('price');
+               return false;      
+    }
+    if ($_SESSION['PO']->trans_type == ST_PURCHORDER && !is_date($_POST['req_del_date'])){
+               display_error(_("The date entered is in an invalid format."));
+               set_focus('req_del_date');
+               return false;            
+    }
+     
+    return true;       
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function handle_update_item()
+{
+       $allow_update = check_data(); 
+
+       if ($allow_update)
+       {
+               if ($_SESSION['PO']->line_items[$_POST['line_no']]->qty_inv > input_num('qty') ||
+                       $_SESSION['PO']->line_items[$_POST['line_no']]->qty_received > input_num('qty'))
+               {
+                       display_error(_("You are attempting to make the quantity ordered a quantity less than has already been invoiced or received.  This is prohibited.") .
+                               "<br>" . _("The quantity received can only be modified by entering a negative receipt and the quantity invoiced can only be reduced by entering a credit note against this item."));
+                       set_focus('qty');
+                       return;
+               }
+       
+               $_SESSION['PO']->update_order_item($_POST['line_no'], input_num('qty'), input_num('price'),
+                       @$_POST['req_del_date'], $_POST['item_description'] );
+               unset_form_variables();
+       }       
+    line_start_focus();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function handle_add_new_item()
+{
+       $allow_update = check_data();
+       
+       if ($allow_update == true)
+       { 
+               if (count($_SESSION['PO']->line_items) > 0)
+               {
+                   foreach ($_SESSION['PO']->line_items as $order_item) 
+                   {
+                       /* do a loop round the items on the order to see that the item
+                       is not already on this order */
+                           if (($order_item->stock_id == $_POST['stock_id'])) 
+                           {
+                                       display_warning(_("The selected item is already on this order."));
+                           }
+                   } /* end of the foreach loop to look for pre-existing items of the same code */
+               }
+
+               if ($allow_update == true)
+               {
+                       $result = get_short_info($_POST['stock_id']);
+
+                       if (db_num_rows($result) == 0)
+                       {
+                               $allow_update = false;
+                       }
+
+                       if ($allow_update)
+                       {
+                               $myrow = db_fetch($result);
+                               $_SESSION['PO']->add_to_order (count($_SESSION['PO']->line_items), $_POST['stock_id'], input_num('qty'), 
+                                       get_post('stock_id_text'), //$myrow["description"], 
+                                       input_num('price'), '', // $myrow["units"], (retrived in cart)
+                                       $_SESSION['PO']->trans_type == ST_PURCHORDER ? $_POST['req_del_date'] : '', 0, 0);
+
+                               unset_form_variables();
+                               $_POST['stock_id']      = "";
+                       } 
+                       else 
+                       {
+                            display_error(_("The selected item does not exist or it is a kit part and therefore cannot be purchased."));
+                       }
+
+               } /* end of if not already on the order and allow input was true*/
+    }
+       line_start_focus();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function can_commit()
+{
+       global $Refs;
+
+       if (!get_post('supplier_id')) 
+       {
+               display_error(_("There is no supplier selected."));
+               set_focus('supplier_id');
+               return false;
+       } 
+       
+       if (!is_date($_POST['OrderDate'])) 
+       {
+               display_error(_("The entered order date is invalid."));
+               set_focus('OrderDate');
+               return false;
+       } 
+       
+       if ($_SESSION['PO']->trans_type != ST_PURCHORDER && !is_date_in_fiscalyear($_POST['OrderDate'])) 
+       {
+               display_error(_("The entered date is not in fiscal year"));
+               set_focus('OrderDate');
+               return false;
+       }
+
+       if (($_SESSION['PO']->trans_type==ST_SUPPINVOICE) && !is_date($_POST['due_date'])) 
+       {
+               display_error(_("The entered due date is invalid."));
+               set_focus('due_date');
+               return false;
+       } 
+       
+       if (!$_SESSION['PO']->order_no) 
+       {
+       if (!$Refs->is_valid(get_post('ref'))) 
+       {
+               display_error(_("There is no reference entered for this purchase order."));
+                       set_focus('ref');
+               return false;
+       } 
+       
+       if (!is_new_reference(get_post('ref'), $_SESSION['PO']->trans_type)) 
+       {
+               display_error(_("The entered reference is already in use."));
+                       set_focus('ref');
+               return false;
+       }
+       }
+       
+       if ($_SESSION['PO']->trans_type == ST_SUPPINVOICE && !$Refs->is_valid(get_post('supp_ref'))) 
+       {
+               display_error(_("You must enter a supplier's invoice reference."));
+               set_focus('supp_ref');
+               return false;
+       }
+       if ($_SESSION['PO']->trans_type==ST_SUPPINVOICE 
+               && is_reference_already_there($_SESSION['PO']->supplier_id, get_post('supp_ref'), $_SESSION['PO']->order_no))
+       {
+               display_error(_("This invoice number has already been entered. It cannot be entered again.") . " (" . get_post('supp_ref') . ")");
+               set_focus('supp_ref');
+               return false;
+       }
+       if ($_SESSION['PO']->trans_type == ST_PURCHORDER && get_post('delivery_address') == '')
+       {
+               display_error(_("There is no delivery address specified."));
+               set_focus('delivery_address');
+               return false;
+       } 
+       if (get_post('StkLocation') == '')
+       {
+               display_error(_("There is no location specified to move any items into."));
+               set_focus('StkLocation');
+               return false;
+       } 
+       
+       if ($_SESSION['PO']->order_has_items() == false)
+       {
+       display_error (_("The order cannot be placed because there are no lines entered on this order."));
+       return false;
+       }
+               
+       return true;
+}
+
+//---------------------------------------------------------------------------------------------------
+
+function handle_commit_order()
+{
+       $cart = &$_SESSION['PO'];
+
+       if (can_commit()) {
+
+               copy_to_cart();
+               if ($cart->trans_type != ST_PURCHORDER) {
+                       // for direct grn/invoice set same dates for lines as for whole document
+                       foreach ($cart->line_items as $line_no =>$line)
+                               $cart->line_items[$line_no]->req_del_date = $cart->orig_order_date;
+               }
+               if ($cart->order_no == 0) { // new po/grn/invoice
+                       /*its a new order to be inserted */
+                       $ref = $cart->reference;
+                       if ($cart->trans_type != ST_PURCHORDER) {
+                               $cart->reference = 'auto';
+                               begin_transaction();    // all db changes as single transaction for direct document
+                       }
+                       $order_no = add_po($cart);
+                       new_doc_date($cart->orig_order_date); 
+               $cart->order_no = $order_no;
+
+                       if ($cart->trans_type == ST_PURCHORDER) {
+                               unset($_SESSION['PO']);
+                       meta_forward($_SERVER['PHP_SELF'], "AddedID=$order_no");
+               }
+                       //Direct GRN
+                       if ($cart->trans_type == ST_SUPPRECEIVE)
+                               $cart->reference = $ref;
+                       $cart->Comments = $cart->reference; //grn does not hold supp_ref
+                       foreach($cart->line_items as $key => $line)
+                               $cart->line_items[$key]->receive_qty = $line->quantity;
+                       $grn_no = add_grn($cart);
+                       if ($cart->trans_type == ST_SUPPRECEIVE) {
+                               commit_transaction(); // save PO+GRN
+                               unset($_SESSION['PO']);
+                       meta_forward($_SERVER['PHP_SELF'], "AddedGRN=$grn_no");
+                       }
+//                     Direct Purchase Invoice
+                       $inv = new supp_trans(ST_SUPPINVOICE);
+                       $inv->Comments = $cart->Comments;
+                       $inv->supplier_id = $cart->supplier_id;
+                       $inv->tran_date = $cart->orig_order_date;
+                       $inv->due_date = $cart->due_date;
+                       $inv->reference = $ref;
+                       $inv->supp_reference = $cart->supp_ref;
+                       $inv->tax_included = $cart->tax_included;
+                       $supp = get_supplier($cart->supplier_id);
+                       $inv->tax_group_id = $supp['tax_group_id'];
+//                     $inv->ov_discount 'this isn't used at all'
+                       $inv->ov_amount = $inv->ov_gst = 0;
+                       
+                       foreach($cart->line_items as $key => $line) {
+                               $inv->add_grn_to_trans($line->grn_item_id, $line->po_detail_rec, $line->stock_id,
+                                       $line->item_description, $line->receive_qty, 0, $line->receive_qty,
+                                       $line->price, $line->price, true, get_standard_cost($line->stock_id), '');
+                               $inv->ov_amount += round2(($line->receive_qty * $line->price), user_price_dec());
+                       }
+                       $taxes = $inv->get_taxes($inv->tax_group_id, 0, false);
+                       foreach( $taxes as $taxitem) {
+                               $inv->ov_gst += round2($taxitem['Value'], user_price_dec());
+                       }
+                       $inv_no = add_supp_invoice($inv);
+                       commit_transaction(); // save PO+GRN+PI
+                       // FIXME payment for cash terms. (Needs cash account selection)
+                       unset($_SESSION['PO']);
+                       meta_forward($_SERVER['PHP_SELF'], "AddedPI=$inv_no");
+               }
+               else { // order modification
+               
+                       $order_no = update_po($cart);
+                       unset($_SESSION['PO']);
+               meta_forward($_SERVER['PHP_SELF'], "AddedID=$order_no&Updated=1");      
+               }
+       }
+}
+//---------------------------------------------------------------------------------------------------
+$id = find_submit('Delete');
+if ($id != -1)
+       handle_delete_item($id);
+
+if (isset($_POST['Commit']))
+{
+       handle_commit_order();
+}
+if (isset($_POST['UpdateLine']))
+       handle_update_item();
+
+if (isset($_POST['EnterLine']))
+       handle_add_new_item();
+
+if (isset($_POST['CancelOrder'])) 
+       handle_cancel_po();
+
+if (isset($_POST['CancelUpdate']))
+       unset_form_variables();
+
+if (isset($_POST['CancelUpdate']) || isset($_POST['UpdateLine'])) {
+       line_start_focus();
+}
+
+//---------------------------------------------------------------------------------------------------
+$textcart_mgr = new POTextCartManager();
+$textcart_mgr->handle_post_request();
+
+function display_order_in_tab ($title, $cart) {
+  display_po_items($cart);
+}
+
+start_form();
+
+display_po_header($_SESSION['PO']);
+echo "<br>";
+
+$textcart_mgr->tab_display('', &$_SESSION['PO'], "display_order_in_tab");
+
+start_table(TABLESTYLE2);
+textarea_row(_("Memo:"), 'Comments', null, 70, 4);
+
+end_table(1);
+
+div_start('controls', 'items_table');
+$process_txt = _("Place Order");
+$update_txt = _("Update Order");
+$cancel_txt = _("Cancel Order");
+if ($_SESSION['PO']->trans_type == ST_SUPPRECEIVE) {
+       $process_txt = _("Process GRN");
+       $update_txt = _("Update GRN");
+       $cancel_txt = _("Cancel GRN");
+}      
+elseif ($_SESSION['PO']->trans_type == ST_SUPPINVOICE) {
+       $process_txt = _("Process Invoice");
+       $update_txt = _("Update Invoice");
+       $cancel_txt = _("Cancel Invoice");
+}      
+if ($_SESSION['PO']->order_has_items()) 
+{
+       if ($_SESSION['PO']->order_no)
+               submit_center_first('Commit', $update_txt, '', 'default');
+       else
+               submit_center_first('Commit', $process_txt, '', 'default');
+       submit_center_last('CancelOrder', $cancel_txt);         
+}
+else
+       submit_center('CancelOrder', $cancel_txt, true, false, 'cancel');
+div_end();
+//---------------------------------------------------------------------------------------------------
+
+end_form();
+end_page();
+?>
diff --git a/sales/sales_order_entry.php b/sales/sales_order_entry.php
new file mode 100644 (file)
index 0000000..60c1ff1
--- /dev/null
@@ -0,0 +1,744 @@
+<?php
+/**********************************************************************
+    Copyright (C) FrontAccounting, LLC.
+       Released under the terms of the GNU General Public License, GPL, 
+       as published by the Free Software Foundation, either version 3 
+       of the License, or (at your option) any later version.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+    See the License here <http://www.gnu.org/licenses/gpl-3.0.html>.
+***********************************************************************/
+//-----------------------------------------------------------------------------
+//
+//     Entry/Modify Sales Quotations
+//     Entry/Modify Sales Order
+//     Entry Direct Delivery
+//     Entry Direct Invoice
+//
+
+$path_to_root = "../../..";
+$page_security = 'SA_SALESORDER';
+
+include_once($path_to_root . "/sales/includes/cart_class.inc");
+include_once($path_to_root . "/includes/session.inc");
+include_once($path_to_root . "/sales/includes/sales_ui.inc");
+include_once($path_to_root . "/sales/includes/ui/sales_order_ui.inc");
+include_once($path_to_root . "/sales/includes/sales_db.inc");
+include_once($path_to_root . "/sales/includes/db/sales_types_db.inc");
+include_once($path_to_root . "/reporting/includes/reporting.inc");
+include_once($path_to_root . "/modules/textcart/includes/textcart_manager.inc");
+
+set_page_security( @$_SESSION['Items']->trans_type,
+       array(  ST_SALESORDER=>'SA_SALESORDER',
+                       ST_SALESQUOTE => 'SA_SALESQUOTE',
+                       ST_CUSTDELIVERY => 'SA_SALESDELIVERY',
+                       ST_SALESINVOICE => 'SA_SALESINVOICE'),
+       array(  'NewOrder' => 'SA_SALESORDER',
+                       'ModifySalesOrder' => 'SA_SALESORDER',
+                       'NewQuotation' => 'SA_SALESQUOTE',
+                       'ModifyQuotationNumber' => 'SA_SALESQUOTE',
+                       'NewDelivery' => 'SA_SALESDELIVERY',
+                       'NewInvoice' => 'SA_SALESINVOICE')
+);
+
+$js = '';
+
+if ($use_popup_windows) {
+       $js .= get_js_open_window(900, 500);
+}
+
+if ($use_date_picker) {
+       $js .= get_js_date_picker();
+}
+
+if (isset($_GET['NewDelivery']) && is_numeric($_GET['NewDelivery'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Direct Sales Delivery");
+       create_cart(ST_CUSTDELIVERY, $_GET['NewDelivery']);
+
+} elseif (isset($_GET['NewInvoice']) && is_numeric($_GET['NewInvoice'])) {
+
+       $_SESSION['page_title'] = _($help_context = "Direct Sales Invoice");
+       create_cart(ST_SALESINVOICE, $_GET['NewInvoice']);
+
+} elseif (isset($_GET['ModifyOrderNumber']) && is_numeric($_GET['ModifyOrderNumber'])) {
+
+       $help_context = 'Modifying Sales Order';
+       $_SESSION['page_title'] = sprintf( _("Modifying Sales Order # %d"), $_GET['ModifyOrderNumber']);
+       create_cart(ST_SALESORDER, $_GET['ModifyOrderNumber']);
+
+} elseif (isset($_GET['ModifyQuotationNumber']) && is_numeric($_GET['ModifyQuotationNumber'])) {
+
+       $help_context = 'Modifying Sales Quotation';
+       $_SESSION['page_title'] = sprintf( _("Modifying Sales Quotation # %d"), $_GET['ModifyQuotationNumber']);
+       create_cart(ST_SALESQUOTE, $_GET['ModifyQuotationNumber']);
+
+} elseif (isset($_GET['NewOrder'])) {
+
+       $_SESSION['page_title'] = _($help_context = "New Sales Order Entry");
+       create_cart(ST_SALESORDER, 0);
+} elseif (isset($_GET['NewQuotation'])) {
+
+       $_SESSION['page_title'] = _($help_context = "New Sales Quotation Entry");
+       create_cart(ST_SALESQUOTE, 0);
+} elseif (isset($_GET['NewQuoteToSalesOrder'])) {
+       $_SESSION['page_title'] = _($help_context = "Sales Order Entry");
+       create_cart(ST_SALESQUOTE, $_GET['NewQuoteToSalesOrder']);
+}
+
+page($_SESSION['page_title'], false, false, "", $js);
+//-----------------------------------------------------------------------------
+
+if (list_updated('branch_id')) {
+       // when branch is selected via external editor also customer can change
+       $br = get_branch(get_post('branch_id'));
+       $_POST['customer_id'] = $br['debtor_no'];
+       $Ajax->activate('customer_id');
+}
+
+if (isset($_GET['AddedID'])) {
+       $order_no = $_GET['AddedID'];
+       display_notification_centered(sprintf( _("Order # %d has been entered."),$order_no));
+
+       submenu_view(_("&View This Order"), ST_SALESORDER, $order_no);
+
+       submenu_print(_("&Print This Order"), ST_SALESORDER, $order_no, 'prtopt');
+       submenu_print(_("&Email This Order"), ST_SALESORDER, $order_no, null, 1);
+       set_focus('prtopt');
+       
+       submenu_option(_("Make &Delivery Against This Order"),
+               "/sales/customer_delivery.php?OrderNumber=$order_no");
+
+       submenu_option(_("Work &Order Entry"),  "/manufacturing/work_order_entry.php?");
+
+       submenu_option(_("Enter a &New Order"), "/sales/sales_order_entry.php?NewOrder=0");
+
+       display_footer_exit();
+
+} elseif (isset($_GET['UpdatedID'])) {
+       $order_no = $_GET['UpdatedID'];
+
+       display_notification_centered(sprintf( _("Order # %d has been updated."),$order_no));
+
+       submenu_view(_("&View This Order"), ST_SALESORDER, $order_no);
+
+       submenu_print(_("&Print This Order"), ST_SALESORDER, $order_no, 'prtopt');
+       submenu_print(_("&Email This Order"), ST_SALESORDER, $order_no, null, 1);
+       set_focus('prtopt');
+
+       submenu_option(_("Confirm Order Quantities and Make &Delivery"),
+               "/sales/customer_delivery.php?OrderNumber=$order_no");
+
+       submenu_option(_("Select A Different &Order"),
+               "/sales/inquiry/sales_orders_view.php?OutstandingOnly=1");
+
+       display_footer_exit();
+
+} elseif (isset($_GET['AddedQU'])) {
+       $order_no = $_GET['AddedQU'];
+       display_notification_centered(sprintf( _("Quotation # %d has been entered."),$order_no));
+
+       submenu_view(_("&View This Quotation"), ST_SALESQUOTE, $order_no);
+
+       submenu_print(_("&Print This Quotation"), ST_SALESQUOTE, $order_no, 'prtopt');
+       submenu_print(_("&Email This Quotation"), ST_SALESQUOTE, $order_no, null, 1);
+       set_focus('prtopt');
+       
+       submenu_option(_("Make &Sales Order Against This Quotation"),
+               "/sales/sales_order_entry.php?NewQuoteToSalesOrder=$order_no");
+
+       submenu_option(_("Enter a New &Quotation"),     "/sales/sales_order_entry.php?NewQuotation=0");
+
+       display_footer_exit();
+
+} elseif (isset($_GET['UpdatedQU'])) {
+       $order_no = $_GET['UpdatedQU'];
+
+       display_notification_centered(sprintf( _("Quotation # %d has been updated."),$order_no));
+
+       submenu_view(_("&View This Quotation"), ST_SALESQUOTE, $order_no);
+
+       submenu_print(_("&Print This Quotation"), ST_SALESQUOTE, $order_no, 'prtopt');
+       submenu_print(_("&Email This Quotation"), ST_SALESQUOTE, $order_no, null, 1);
+       set_focus('prtopt');
+
+       submenu_option(_("Make &Sales Order Against This Quotation"),
+               "/sales/sales_order_entry.php?NewQuoteToSalesOrder=$order_no");
+
+       submenu_option(_("Select A Different &Quotation"),
+               "/sales/inquiry/sales_orders_view.php?type=".ST_SALESQUOTE);
+
+       display_footer_exit();
+} elseif (isset($_GET['AddedDN'])) {
+       $delivery = $_GET['AddedDN'];
+
+       display_notification_centered(sprintf(_("Delivery # %d has been entered."),$delivery));
+
+       submenu_view(_("&View This Delivery"), ST_CUSTDELIVERY, $delivery);
+
+       submenu_print(_("&Print Delivery Note"), ST_CUSTDELIVERY, $delivery, 'prtopt');
+       submenu_print(_("&Email Delivery Note"), ST_CUSTDELIVERY, $delivery, null, 1);
+       submenu_print(_("P&rint as Packing Slip"), ST_CUSTDELIVERY, $delivery, 'prtopt', null, 1);
+       submenu_print(_("E&mail as Packing Slip"), ST_CUSTDELIVERY, $delivery, null, 1, 1);
+       set_focus('prtopt');
+
+       display_note(get_gl_view_str(ST_CUSTDELIVERY, $delivery, _("View the GL Journal Entries for this Dispatch")),0, 1);
+
+       submenu_option(_("Make &Invoice Against This Delivery"),
+               "/sales/customer_invoice.php?DeliveryNumber=$delivery");
+
+       if ((isset($_GET['Type']) && $_GET['Type'] == 1))
+               submenu_option(_("Enter a New Template &Delivery"),
+                       "/sales/inquiry/sales_orders_view.php?DeliveryTemplates=Yes");
+       else
+               submenu_option(_("Enter a &New Delivery"), 
+                       "/sales/sales_order_entry.php?NewDelivery=0");
+
+       display_footer_exit();
+
+} elseif (isset($_GET['AddedDI'])) {
+       $invoice = $_GET['AddedDI'];
+
+       display_notification_centered(sprintf(_("Invoice # %d has been entered."), $invoice));
+
+       submenu_view(_("&View This Invoice"), ST_SALESINVOICE, $invoice);
+
+       submenu_print(_("&Print Sales Invoice"), ST_SALESINVOICE, $invoice."-".ST_SALESINVOICE, 'prtopt');
+       submenu_print(_("&Email Sales Invoice"), ST_SALESINVOICE, $invoice."-".ST_SALESINVOICE, null, 1);
+       set_focus('prtopt');
+       
+       $sql = "SELECT trans_type_from, trans_no_from FROM ".TB_PREF."cust_allocations
+                       WHERE trans_type_to=".ST_SALESINVOICE." AND trans_no_to=".db_escape($invoice);
+       $result = db_query($sql, "could not retrieve customer allocation");
+       $row = db_fetch($result);
+       if ($row !== false)
+               submenu_print(_("Print &Receipt"), $row['trans_type_from'], $row['trans_no_from']."-".$row['trans_type_from'], 'prtopt');
+
+       display_note(get_gl_view_str(ST_SALESINVOICE, $invoice, _("View the GL &Journal Entries for this Invoice")),0, 1);
+
+       if ((isset($_GET['Type']) && $_GET['Type'] == 1))
+               submenu_option(_("Enter a &New Template Invoice"), 
+                       "/sales/inquiry/sales_orders_view.php?InvoiceTemplates=Yes");
+       else
+               submenu_option(_("Enter a &New Direct Invoice"),
+                       "/sales/sales_order_entry.php?NewInvoice=0");
+
+       submenu_option(_("Add an Attachment"), "/admin/attachments.php?filterType=".ST_SALESINVOICE."&trans_no=$invoice");
+
+       display_footer_exit();
+} else
+       check_edit_conflicts();
+//-----------------------------------------------------------------------------
+
+function copy_to_cart()
+{
+       $cart = &$_SESSION['Items'];
+
+       $cart->reference = $_POST['ref'];
+
+       $cart->Comments =  $_POST['Comments'];
+
+       $cart->document_date = $_POST['OrderDate'];
+
+       $newpayment = false;
+       if (isset($_POST['payment']) && ($cart->payment != $_POST['payment'])) {
+               $cart->payment = $_POST['payment'];
+               $cart->payment_terms = get_payment_terms($_POST['payment']);
+               $newpayment = true;
+       }
+       if ($cart->payment_terms['cash_sale']) {
+               if ($newpayment) {
+                       $cart->due_date = $cart->document_date;
+                       $cart->phone = $cart->cust_ref = $cart->delivery_address = '';
+                       $cart->ship_via = 1;
+                       $cart->deliver_to = '';
+               }
+       } else {
+               $cart->due_date = $_POST['delivery_date'];
+               $cart->cust_ref = $_POST['cust_ref'];
+               $cart->deliver_to = $_POST['deliver_to'];
+               $cart->delivery_address = $_POST['delivery_address'];
+               $cart->phone = $_POST['phone'];
+               $cart->ship_via = $_POST['ship_via'];
+       }
+       $cart->Location = $_POST['Location'];
+       $cart->freight_cost = input_num('freight_cost');
+       if (isset($_POST['email']))
+               $cart->email =$_POST['email'];
+       else
+               $cart->email = '';
+       $cart->customer_id      = $_POST['customer_id'];
+       $cart->Branch = $_POST['branch_id'];
+       $cart->sales_type = $_POST['sales_type'];
+
+       if ($cart->trans_type!=ST_SALESORDER && $cart->trans_type!=ST_SALESQUOTE) { // 2008-11-12 Joe Hunt
+               $cart->dimension_id = $_POST['dimension_id'];
+               $cart->dimension2_id = $_POST['dimension2_id'];
+       }
+}
+
+//-----------------------------------------------------------------------------
+
+function copy_from_cart()
+{
+       $cart = &$_SESSION['Items'];
+       $_POST['ref'] = $cart->reference;
+       $_POST['Comments'] = $cart->Comments;
+
+       $_POST['OrderDate'] = $cart->document_date;
+       $_POST['delivery_date'] = $cart->due_date;
+       $_POST['cust_ref'] = $cart->cust_ref;
+       $_POST['freight_cost'] = price_format($cart->freight_cost);
+
+       $_POST['deliver_to'] = $cart->deliver_to;
+       $_POST['delivery_address'] = $cart->delivery_address;
+       $_POST['phone'] = $cart->phone;
+       $_POST['Location'] = $cart->Location;
+       $_POST['ship_via'] = $cart->ship_via;
+
+       $_POST['customer_id'] = $cart->customer_id;
+
+       $_POST['branch_id'] = $cart->Branch;
+       $_POST['sales_type'] = $cart->sales_type;
+       // POS 
+       $_POST['payment'] = $cart->payment;
+       if ($cart->trans_type!=ST_SALESORDER && $cart->trans_type!=ST_SALESQUOTE) { // 2008-11-12 Joe Hunt
+               $_POST['dimension_id'] = $cart->dimension_id;
+               $_POST['dimension2_id'] = $cart->dimension2_id;
+       }       
+       $_POST['cart_id'] = $cart->cart_id;
+               
+}
+//--------------------------------------------------------------------------------
+
+function line_start_focus() {
+  global       $Ajax;
+
+  $Ajax->activate('items_table');
+  set_focus('_stock_id_edit');
+}
+
+//--------------------------------------------------------------------------------
+function can_process() {
+       global $Refs;
+
+       if (!get_post('customer_id')) 
+       {
+               display_error(_("There is no customer selected."));
+               set_focus('customer_id');
+               return false;
+       } 
+       
+       if (!get_post('branch_id')) 
+       {
+               display_error(_("This customer has no branch defined."));
+               set_focus('branch_id');
+               return false;
+       } 
+       
+       if (!is_date($_POST['OrderDate'])) {
+               display_error(_("The entered date is invalid."));
+               set_focus('OrderDate');
+               return false;
+       }
+       if ($_SESSION['Items']->trans_type!=ST_SALESORDER && $_SESSION['Items']->trans_type!=ST_SALESQUOTE && !is_date_in_fiscalyear($_POST['OrderDate'])) {
+               display_error(_("The entered date is not in fiscal year"));
+               set_focus('OrderDate');
+               return false;
+       }
+       if (count($_SESSION['Items']->line_items) == 0) {
+               display_error(_("You must enter at least one non empty item line."));
+               set_focus('AddItem');
+               return false;
+       }
+       if ($_SESSION['Items']->payment_terms['cash_sale'] == 0) {
+       if (strlen($_POST['deliver_to']) <= 1) {
+               display_error(_("You must enter the person or company to whom delivery should be made to."));
+               set_focus('deliver_to');
+               return false;
+       }
+
+
+               if ($_SESSION['Items']->trans_type != ST_SALESQUOTE && strlen($_POST['delivery_address']) <= 1) {
+                       display_error( _("You should enter the street address in the box provided. Orders cannot be accepted without a valid street address."));
+                       set_focus('delivery_address');
+                       return false;
+               }
+
+               if ($_POST['freight_cost'] == "")
+                       $_POST['freight_cost'] = price_format(0);
+
+               if (!check_num('freight_cost',0)) {
+                       display_error(_("The shipping cost entered is expected to be numeric."));
+                       set_focus('freight_cost');
+                       return false;
+               }
+               if (!is_date($_POST['delivery_date'])) {
+                       if ($_SESSION['Items']->trans_type==ST_SALESQUOTE)
+                               display_error(_("The Valid date is invalid."));
+                       else    
+                               display_error(_("The delivery date is invalid."));
+                       set_focus('delivery_date');
+                       return false;
+               }
+               //if (date1_greater_date2($_SESSION['Items']->document_date, $_POST['delivery_date'])) {
+               if (date1_greater_date2($_POST['OrderDate'], $_POST['delivery_date'])) {
+                       if ($_SESSION['Items']->trans_type==ST_SALESQUOTE)
+                               display_error(_("The requested valid date is before the date of the quotation."));
+                       else    
+                               display_error(_("The requested delivery date is before the date of the order."));
+                       set_focus('delivery_date');
+                       return false;
+               }
+       }
+       else
+       {
+               if (!db_has_cash_accounts())
+               {
+                       display_error(_("You need to define a cash account for your Sales Point."));
+                       return false;
+               }       
+       }       
+       if (!$Refs->is_valid($_POST['ref'])) {
+               display_error(_("You must enter a reference."));
+               set_focus('ref');
+               return false;
+       }
+       if ($_SESSION['Items']->trans_no==0 && !is_new_reference($_POST['ref'], 
+               $_SESSION['Items']->trans_type)) {
+               display_error(_("The entered reference is already in use."));
+               set_focus('ref');
+               return false;
+       } elseif ($_SESSION['Items']->get_items_total() < 0) {
+               display_error("Invoice total amount cannot be less than zero.");
+               return false;
+       }
+       return true;
+}
+
+//-----------------------------------------------------------------------------
+
+if (isset($_POST['update'])) {
+       copy_to_cart();
+       $Ajax->activate('items_table');
+}
+
+if (isset($_POST['ProcessOrder']) && can_process()) {
+       copy_to_cart();
+       $modified = ($_SESSION['Items']->trans_no != 0);
+       $so_type = $_SESSION['Items']->so_type;
+       
+       $_SESSION['Items']->write(1);
+       if (count($messages)) { // abort on failure or error messages are lost
+               $Ajax->activate('_page_body');
+               display_footer_exit();
+       }
+       $trans_no = key($_SESSION['Items']->trans_no);
+       $trans_type = $_SESSION['Items']->trans_type;
+       new_doc_date($_SESSION['Items']->document_date);
+       processing_end();
+       if ($modified) {
+               if ($trans_type == ST_SALESQUOTE)
+                       meta_forward($_SERVER['PHP_SELF'], "UpdatedQU=$trans_no");
+               else    
+                       meta_forward($_SERVER['PHP_SELF'], "UpdatedID=$trans_no");
+       } elseif ($trans_type == ST_SALESORDER) {
+               meta_forward($_SERVER['PHP_SELF'], "AddedID=$trans_no");
+       } elseif ($trans_type == ST_SALESQUOTE) {
+               meta_forward($_SERVER['PHP_SELF'], "AddedQU=$trans_no");
+       } elseif ($trans_type == ST_SALESINVOICE) {
+               meta_forward($_SERVER['PHP_SELF'], "AddedDI=$trans_no&Type=$so_type");
+       } else {
+               meta_forward($_SERVER['PHP_SELF'], "AddedDN=$trans_no&Type=$so_type");
+       }
+}
+
+//--------------------------------------------------------------------------------
+
+function check_item_data()
+{
+       global $SysPrefs, $allow_negative_prices;
+       
+       $is_inventory_item = is_inventory_item(get_post('stock_id'));
+       if(!get_post('stock_id_text', true)) {
+               display_error( _("Item description cannot be empty."));
+               set_focus('stock_id_edit');
+               return false;
+       }
+       elseif (!check_num('qty', 0) || !check_num('Disc', 0, 100)) {
+               display_error( _("The item could not be updated because you are attempting to set the quantity ordered to less than 0, or the discount percent to more than 100."));
+               set_focus('qty');
+               return false;
+       } elseif (!check_num('price', 0) && (!$allow_negative_prices || $is_inventory_item)) {
+               display_error( _("Price for inventory item must be entered and can not be less than 0"));
+               set_focus('price');
+               return false;
+       } elseif (isset($_POST['LineNo']) && isset($_SESSION['Items']->line_items[$_POST['LineNo']])
+           && !check_num('qty', $_SESSION['Items']->line_items[$_POST['LineNo']]->qty_done)) {
+
+               set_focus('qty');
+               display_error(_("You attempting to make the quantity ordered a quantity less than has already been delivered. The quantity delivered cannot be modified retrospectively."));
+               return false;
+       } // Joe Hunt added 2008-09-22 -------------------------
+       elseif ($is_inventory_item && $_SESSION['Items']->trans_type!=ST_SALESORDER && $_SESSION['Items']->trans_type!=ST_SALESQUOTE 
+               && !$SysPrefs->allow_negative_stock())
+       {
+               $qoh = get_qoh_on_date($_POST['stock_id'], $_POST['Location'], $_POST['OrderDate']);
+               if (input_num('qty') > $qoh)
+               {
+                       $stock = get_item($_POST['stock_id']);
+                       display_error(_("The delivery cannot be processed because there is an insufficient quantity for item:") .
+                               " " . $stock['stock_id'] . " - " . $stock['description'] . " - " .
+                               _("Quantity On Hand") . " = " . number_format2($qoh, get_qty_dec($_POST['stock_id'])));
+                       return false;
+               }
+               return true;
+       }
+       $cost_home = get_standard_cost(get_post('stock_id')); // Added 2011-03-27 Joe Hunt
+       $cost = $cost_home / get_exchange_rate_from_home_currency($_SESSION['Items']->customer_currency, $_SESSION['Items']->document_date);
+       if (input_num('price') < $cost)
+       {
+               $dec = user_price_dec();
+               $curr = $_SESSION['Items']->customer_currency;
+               $price = number_format2(input_num('price'), $dec);
+               if ($cost_home == $cost)
+                       $std_cost = number_format2($cost_home, $dec);
+               else
+               {
+                       $price = $curr . " " . $price;
+                       $std_cost = $curr . " " . number_format2($cost, $dec);
+               }
+               display_warning(sprintf(_("Price %s is below Standard Cost %s"), $price, $std_cost));
+       }       
+       return true;
+}
+
+//--------------------------------------------------------------------------------
+
+function handle_update_item()
+{
+       if ($_POST['UpdateItem'] != '' && check_item_data()) {
+               $_SESSION['Items']->update_cart_item($_POST['LineNo'],
+                input_num('qty'), input_num('price'),
+                input_num('Disc') / 100, $_POST['item_description'] );
+       }
+       page_modified();
+  line_start_focus();
+}
+
+//--------------------------------------------------------------------------------
+
+function handle_delete_item($line_no)
+{
+    if ($_SESSION['Items']->some_already_delivered($line_no) == 0) {
+           $_SESSION['Items']->remove_from_cart($line_no);
+    } else {
+       display_error(_("This item cannot be deleted because some of it has already been delivered."));
+    }
+    line_start_focus();
+}
+
+//--------------------------------------------------------------------------------
+
+function handle_new_item()
+{
+
+       if (!check_item_data()) {
+                       return;
+       }
+       add_to_order($_SESSION['Items'], get_post('stock_id'), input_num('qty'),
+               input_num('price'), input_num('Disc') / 100, get_post('stock_id_text'));
+
+       unset($_POST['_stock_id_edit'], $_POST['stock_id']);
+       page_modified();
+       line_start_focus();
+}
+
+//--------------------------------------------------------------------------------
+
+function  handle_cancel_order()
+{
+       global $path_to_root, $Ajax;
+
+
+       if ($_SESSION['Items']->trans_type == ST_CUSTDELIVERY) {
+               display_notification(_("Direct delivery entry has been cancelled as requested."), 1);
+               submenu_option(_("Enter a New Sales Delivery"), "/sales/sales_order_entry.php?NewDelivery=1");
+
+       } elseif ($_SESSION['Items']->trans_type == ST_SALESINVOICE) {
+               display_notification(_("Direct invoice entry has been cancelled as requested."), 1);
+               submenu_option(_("Enter a New Sales Invoice"),  "/sales/sales_order_entry.php?NewInvoice=1");
+       } else {
+               if ($_SESSION['Items']->trans_no != 0) {
+                       if ($_SESSION['Items']->trans_type == ST_SALESORDER && 
+                               sales_order_has_deliveries(key($_SESSION['Items']->trans_no)))
+                               display_error(_("This order cannot be cancelled because some of it has already been invoiced or dispatched. However, the line item quantities may be modified."));
+                       else {
+                               delete_sales_order(key($_SESSION['Items']->trans_no), $_SESSION['Items']->trans_type);
+                               if ($_SESSION['Items']->trans_type == ST_SALESQUOTE)
+                               {
+                                       display_notification(_("This sales quotation has been cancelled as requested."), 1);
+                                       submenu_option(_("Enter a New Sales Quotation"), "/sales/sales_order_entry.php?NewQuotation=Yes");
+                               }
+                               else
+                               {
+                                       display_notification(_("This sales order has been cancelled as requested."), 1);
+                                       submenu_option(_("Enter a New Sales Order"), "/sales/sales_order_entry.php?NewOrder=Yes");
+                               }
+                       }       
+               } else {
+                       processing_end();
+                       meta_forward($path_to_root.'/index.php','application=orders');
+               }
+       }
+       $Ajax->activate('_page_body');
+       processing_end();
+       display_footer_exit();
+}
+
+//--------------------------------------------------------------------------------
+
+function create_cart($type, $trans_no)
+{ 
+       global $Refs;
+
+       if (!$_SESSION['SysPrefs']->db_ok) // create_cart is called before page() where the check is done
+               return;
+
+       processing_start();
+
+       if (isset($_GET['NewQuoteToSalesOrder']))
+       {
+               $trans_no = $_GET['NewQuoteToSalesOrder'];
+               $doc = new Cart(ST_SALESQUOTE, $trans_no, true);
+               $doc->Comments = _("Sales Quotation") . " # " . $trans_no;
+               $_SESSION['Items'] = $doc;
+       }       
+       elseif($type != ST_SALESORDER && $type != ST_SALESQUOTE && $trans_no != 0) { // this is template
+
+               $doc = new Cart(ST_SALESORDER, array($trans_no));
+               $doc->trans_type = $type;
+               $doc->trans_no = 0;
+               $doc->document_date = new_doc_date();
+               if ($type == ST_SALESINVOICE) {
+                       $doc->due_date = get_invoice_duedate($doc->payment, $doc->document_date);
+                       $doc->pos = get_sales_point(user_pos());
+               } else
+                       $doc->due_date = $doc->document_date;
+               $doc->reference = $Refs->get_next($doc->trans_type);
+               //$doc->Comments='';
+               foreach($doc->line_items as $line_no => $line) {
+                       $doc->line_items[$line_no]->qty_done = 0;
+               }
+               $_SESSION['Items'] = $doc;
+       } else
+               $_SESSION['Items'] = new Cart($type, array($trans_no));
+       copy_from_cart();
+}
+
+//--------------------------------------------------------------------------------
+
+function handle_textcart($clear_cart=false, $default_mode=null) {
+  if (!isset($_POST['textcart'])) {
+    return;
+  }
+  $cart = $_SESSION['Items'];
+  $text = $_POST['textcart'];
+  if ($clear_cart) { $cart->line_items = array(); } // clear_items doesn't work, can't update the price if we are using it
+  process_textcart($cart, $text, $default_mode);
+}
+//--------------------------------------------------------------------------------
+
+if (isset($_POST['CancelOrder']))
+       handle_cancel_order();
+
+$id = find_submit('Delete');
+if ($id!=-1)
+       handle_delete_item($id);
+
+if (isset($_POST['UpdateItem']))
+       handle_update_item();
+
+if (isset($_POST['AddItem']))
+       handle_new_item();
+
+if (isset($_POST['CancelItemChanges'])) {
+       line_start_focus();
+}
+
+//--------------------------------------------------------------------------------
+check_db_has_stock_items(_("There are no inventory items defined in the system."));
+
+check_db_has_customer_branches(_("There are no customers, or there are no customers with branches. Please define customers and customer branches."));
+
+if ($_SESSION['Items']->trans_type == ST_SALESINVOICE) {
+       $idate = _("Invoice Date:");
+       $orderitems = _("Sales Invoice Items");
+       $deliverydetails = _("Enter Delivery Details and Confirm Invoice");
+       $cancelorder = _("Cancel Invoice");
+       $porder = _("Place Invoice");
+} elseif ($_SESSION['Items']->trans_type == ST_CUSTDELIVERY) {
+       $idate = _("Delivery Date:");
+       $orderitems = _("Delivery Note Items");
+       $deliverydetails = _("Enter Delivery Details and Confirm Dispatch");
+       $cancelorder = _("Cancel Delivery");
+       $porder = _("Place Delivery");
+} elseif ($_SESSION['Items']->trans_type == ST_SALESQUOTE) {
+       $idate = _("Quotation Date:");
+       $orderitems = _("Sales Quotation Items");
+       $deliverydetails = _("Enter Delivery Details and Confirm Quotation");
+       $cancelorder = _("Cancel Quotation");
+       $porder = _("Place Quotation");
+       $corder = _("Commit Quotations Changes");
+} else {
+       $idate = _("Order Date:");
+       $orderitems = _("Sales Order Items");
+       $deliverydetails = _("Enter Delivery Details and Confirm Order");
+       $cancelorder = _("Cancel Order");
+       $porder = _("Place Order");
+       $corder = _("Commit Order Changes");
+}
+$textcart_mgr = new SalesTextCartManager();
+$textcart_mgr->handle_post_request();
+function display_order_in_tab($title, $cart) {
+  display_order_summary($title, $cart, true);
+}
+
+start_form();
+
+hidden('cart_id');
+$customer_error = display_order_header($_SESSION['Items'],
+       ($_SESSION['Items']->any_already_delivered() == 0), $idate);
+
+if ($customer_error == "") {
+       start_table(TABLESTYLE, "width=80%", 10);
+       echo "<tr><td>";
+  $textcart_mgr->tab_display($orderitems
+    ,$_SESSION['Items']
+    ,"display_order_in_tab"
+  );
+       echo "</td></tr>";
+       echo "<tr><td>";
+       display_delivery_details($_SESSION['Items']);
+       echo "</td></tr>";
+       end_table(1);
+
+       if ($_SESSION['Items']->trans_no == 0) {
+
+               submit_center_first('ProcessOrder', $porder,
+                   _('Check entered data and save document'), 'default');
+               submit_js_confirm('CancelOrder', _('You are about to void this Document.\nDo you want to continue?'));
+       } else {
+               submit_center_first('ProcessOrder', $corder,
+                   _('Validate changes and update document'), 'default');
+       }
+
+       submit_center_last('CancelOrder', $cancelorder,
+          _('Cancels document entry or removes sales order when editing an old document'));
+} else {
+       display_error($customer_error);
+}
+end_form();
+end_page();
+?>