[POS-commit] r8188 - in stoqlib/trunk: data/glade data/sql stoqlib/database stoqlib/domain stoqlib/domain/test stoqlib/gui/editors stoqlib/gui/wizards

george at async.com.br george at async.com.br
Fri Jul 31 11:01:36 BRT 2009


Author: george
Date: Fri Jul 31 11:01:36 2009
New Revision: 8188

Log:
# Wizard de abertura de producao (stoqlib). r=romaia

Added:
   stoqlib/trunk/data/glade/FinishOpenProductionOrderStep.glade
   stoqlib/trunk/data/glade/OpenProductionOrderStep.glade
   stoqlib/trunk/data/glade/ProductionItemEditor.glade
   stoqlib/trunk/data/sql/patch-01-25.sql
   stoqlib/trunk/stoqlib/domain/test/test_production.py
   stoqlib/trunk/stoqlib/gui/editors/productioneditor.py
   stoqlib/trunk/stoqlib/gui/wizards/productionwizard.py
Modified:
   stoqlib/trunk/data/glade/AbstractItemStep.glade
   stoqlib/trunk/stoqlib/database/tables.py
   stoqlib/trunk/stoqlib/domain/exampledata.py
   stoqlib/trunk/stoqlib/domain/product.py
   stoqlib/trunk/stoqlib/domain/production.py
   stoqlib/trunk/stoqlib/domain/service.py
   stoqlib/trunk/stoqlib/domain/test/test_product.py
   stoqlib/trunk/stoqlib/domain/views.py
   stoqlib/trunk/stoqlib/gui/wizards/abstractwizard.py

Modified: stoqlib/trunk/data/glade/AbstractItemStep.glade
==============================================================================
--- stoqlib/trunk/data/glade/AbstractItemStep.glade	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/data/glade/AbstractItemStep.glade	Fri Jul 31 11:01:36 2009	(r8188)
@@ -14,12 +14,12 @@
                         <property name="spacing">5</property>
                         <property name="visible">True</property>
                         <child>
-                            <widget class="ProxyLabel" id="kiwilabel1">
+                            <widget class="ProxyLabel" id="item_lbl">
                                 <property name="can_focus">True</property>
                                 <property name="data_type">unicode</property>
                                 <property name="is_focus">True</property>
                                 <property name="label" context="yes" translatable="yes">Item:</property>
-                                <property name="model_attribute">kiwilabel1</property>
+                                <property name="model_attribute">item_lbl</property>
                                 <property name="selectable">True</property>
                                 <property name="visible">True</property>
                                 <property name="xalign">1.0</property>
@@ -112,7 +112,7 @@
                                 <property name="digits">2</property>
                                 <property name="is_focus">True</property>
                                 <property name="model_attribute">cost</property>
-                                <property name="text" context="yes" translatable="yes">0.0</property>
+                                <property name="text" context="yes" translatable="yes">0.00</property>
                                 <property name="visible">True</property>
                             </widget>
                             <packing>
@@ -158,9 +158,6 @@
                                             </widget>
                                         </child>
                                     </widget>
-                                    <packing>
-                                        <property name="expand">False</property>
-                                    </packing>
                                 </child>
                                 <child>
                                     <widget class="GtkButton" id="product_button">
@@ -198,7 +195,6 @@
                                         </child>
                                     </widget>
                                     <packing>
-                                        <property name="expand">False</property>
                                         <property name="position">1</property>
                                     </packing>
                                 </child>

Added: stoqlib/trunk/data/glade/FinishOpenProductionOrderStep.glade
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/data/glade/FinishOpenProductionOrderStep.glade	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,70 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://gazpacho.sicem.biz/gazpacho-0.1.dtd">
+<glade-interface domain="stoqlib">
+    <widget class="GtkWindow" id="FinishOpenProductionOrderStep">
+        <property name="default_height">250</property>
+        <property name="default_width">440</property>
+        <child>
+            <widget class="GtkVBox" id="vbox1">
+                <property name="border_width">6</property>
+                <property name="spacing">6</property>
+                <property name="visible">True</property>
+                <child>
+                    <widget class="GtkLabel" id="label1">
+                        <property name="label" context="yes" translatable="yes">&lt;b&gt;Listing all the components needed by the production:&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="expand">False</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ObjectList" id="materials">
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="position">1</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkHBox" id="hbox1">
+                        <property name="spacing">6</property>
+                        <property name="visible">True</property>
+                        <child>
+                            <widget class="ProxyButton" id="edit_button">
+                                <property name="data_type">str</property>
+                                <property name="label">gtk-edit</property>
+                                <property name="model_attribute">edit_button</property>
+                                <property name="use_stock">True</property>
+                                <property name="use_underline">False</property>
+                                <property name="visible">True</property>
+                            </widget>
+                            <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                            </packing>
+                        </child>
+                        <child>
+                            <widget class="ProxyCheckButton" id="start_production_check">
+                                <property name="data_type">bool</property>
+                                <property name="label" context="yes" translatable="yes">Start Production even if there are missing components</property>
+                                <property name="model_attribute">start_production_check</property>
+                                <property name="visible">True</property>
+                            </widget>
+                            <packing>
+                                <property name="position">1</property>
+                            </packing>
+                        </child>
+                    </widget>
+                    <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                    </packing>
+                </child>
+            </widget>
+        </child>
+    </widget>
+</glade-interface>

Added: stoqlib/trunk/data/glade/OpenProductionOrderStep.glade
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/data/glade/OpenProductionOrderStep.glade	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,201 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://gazpacho.sicem.biz/gazpacho-0.1.dtd">
+<glade-interface domain="stoqlib">
+    <widget class="GtkWindow" id="OpenProductionOrderStep">
+        <property name="default_height">250</property>
+        <property name="default_width">440</property>
+        <child>
+            <widget class="GtkTable" id="table">
+                <property name="border_width">12</property>
+                <property name="column_spacing">6</property>
+                <property name="n_columns">4</property>
+                <property name="n_rows">4</property>
+                <property name="row_spacing">6</property>
+                <property name="visible">True</property>
+                <child>
+                    <widget class="GtkLabel" id="label1">
+                        <property name="label" context="yes" translatable="yes">Open Date:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label3">
+                        <property name="label" context="yes" translatable="yes">Branch:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label4">
+                        <property name="label" context="yes" translatable="yes">Responsible:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="top_attach">2</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label5">
+                        <property name="label" context="yes" translatable="yes">Description:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">4</property>
+                        <property name="top_attach">3</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyDateEntry" id="open_date">
+                        <property name="data_type">date</property>
+                        <property name="mandatory">True</property>
+                        <property name="model_attribute">open_date</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyComboEntry" id="branch">
+                        <property name="data_type">object</property>
+                        <property name="mandatory">True</property>
+                        <property name="model_attribute">branch</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyComboEntry" id="responsible">
+                        <property name="data_type">object</property>
+                        <property name="mandatory">True</property>
+                        <property name="model_attribute">responsible</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyEntry" id="description">
+                        <property name="data_type">unicode</property>
+                        <property name="mandatory">True</property>
+                        <property name="model_attribute">description</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">4</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">4</property>
+                        <property name="top_attach">3</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label2">
+                        <property name="label" context="yes" translatable="yes">Number:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label6">
+                        <property name="label" context="yes" translatable="yes">Expected Start Date:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                        <property name="top_attach">1</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <placeholder/>
+                    <packing>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyLabel" id="order_number">
+                        <property name="data_type">unicode</property>
+                        <property name="model_attribute">order_number</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyDateEntry" id="expected_start_date">
+                        <property name="data_type">date</property>
+                        <property name="model_attribute">expected_start_date</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                        <property name="top_attach">1</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <placeholder/>
+                    <packing>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                    </packing>
+                </child>
+            </widget>
+        </child>
+    </widget>
+</glade-interface>

Added: stoqlib/trunk/data/glade/ProductionItemEditor.glade
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/data/glade/ProductionItemEditor.glade	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,170 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://gazpacho.sicem.biz/gazpacho-0.1.dtd">
+<glade-interface domain="stoqlib">
+    <widget class="GtkWindow" id="ProductionItemEditor">
+        <property name="default_height">250</property>
+        <property name="default_width">440</property>
+        <child>
+            <widget class="GtkTable" id="table1">
+                <property name="column_spacing">6</property>
+                <property name="n_columns">4</property>
+                <property name="n_rows">3</property>
+                <property name="row_spacing">6</property>
+                <property name="visible">True</property>
+                <child>
+                    <widget class="GtkLabel" id="label1">
+                        <property name="label" context="yes" translatable="yes">Production:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label2">
+                        <property name="label" context="yes" translatable="yes">Description:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="quantity_lbl">
+                        <property name="label" context="yes" translatable="yes">Quantity:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="top_attach">2</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyLabel" id="order_number">
+                        <property name="data_type">unicode</property>
+                        <property name="model_attribute">order_number</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyLabel" id="description">
+                        <property name="data_type">unicode</property>
+                        <property name="model_attribute">description</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxySpinButton" id="quantity">
+                        <property name="adjustment">1 0 100 1 10 0</property>
+                        <property name="climb_rate">1.0</property>
+                        <property name="data_type">Decimal</property>
+                        <property name="digits">2</property>
+                        <property name="is_focus">True</property>
+                        <property name="model_attribute">quantity</property>
+                        <property name="text" context="yes" translatable="yes">1.00</property>
+                        <property name="visible">True</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="x_options">fill</property>
+                        <property name="y_options">fill</property>
+                    </packing>
+                </child>
+                <child>
+                    <placeholder/>
+                    <packing>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="location_content">
+                        <property name="label" context="yes" translatable="yes">Location:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                        <property name="top_attach">1</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="GtkLabel" id="label3">
+                        <property name="label" context="yes" translatable="yes">Unit:</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">1.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="left_attach">2</property>
+                        <property name="right_attach">3</property>
+                        <property name="top_attach">2</property>
+                    </packing>
+                </child>
+                <child>
+                    <placeholder/>
+                    <packing>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyLabel" id="location">
+                        <property name="data_type">unicode</property>
+                        <property name="model_attribute">location</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">2</property>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                        <property name="top_attach">1</property>
+                    </packing>
+                </child>
+                <child>
+                    <widget class="ProxyLabel" id="unit_description">
+                        <property name="data_type">unicode</property>
+                        <property name="model_attribute">unit_description</property>
+                        <property name="visible">True</property>
+                        <property name="xalign">0.0</property>
+                    </widget>
+                    <packing>
+                        <property name="bottom_attach">3</property>
+                        <property name="left_attach">3</property>
+                        <property name="right_attach">4</property>
+                        <property name="top_attach">2</property>
+                    </packing>
+                </child>
+            </widget>
+        </child>
+    </widget>
+</glade-interface>

Added: stoqlib/trunk/data/sql/patch-01-25.sql
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/data/sql/patch-01-25.sql	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,42 @@
+-- # 4037: Wizard de abertura de produção
+
+ALTER TABLE production_order ADD COLUMN expected_start_date timestamp;
+ALTER TABLE production_order ADD COLUMN start_date timestamp;
+
+CREATE TABLE production_item (
+    id serial NOT NULL PRIMARY KEY,
+    te_created_id bigint UNIQUE REFERENCES transaction_entry(id),
+    te_modified_id bigint UNIQUE REFERENCES transaction_entry(id),
+
+    quantity numeric(10, 2) CONSTRAINT positive_quantity CHECK  (quantity >= 0),
+    product_id bigint REFERENCES product(id),
+    order_id bigint REFERENCES production_order(id)
+);
+
+
+CREATE TABLE production_material (
+    id serial NOT NULL PRIMARY KEY,
+    te_created_id bigint UNIQUE REFERENCES transaction_entry(id),
+    te_modified_id bigint UNIQUE REFERENCES transaction_entry(id),
+
+    needed numeric(10, 2) CONSTRAINT positive_quantity CHECK (needed > 0),
+    consumed numeric(10, 2) DEFAULT 0 CONSTRAINT positive_consumed CHECK (consumed >= 0),
+    allocated numeric(10, 2) DEFAULT 0 CONSTRAINT positive_allocated CHECK (allocated >= 0),
+    to_purchase numeric(10, 2) CONSTRAINT positive_purchase_quantity CHECK (to_purchase >= 0),
+    to_make numeric(10, 2) CONSTRAINT positive_make_quantity CHECK (to_make >= 0),
+    lost numeric(10, 2) DEFAULT 0 CONSTRAINT positive_lost CHECK (lost >= 0),
+
+
+    order_id bigint REFERENCES production_order(id),
+    product_id bigint REFERENCES product(id)
+);
+
+CREATE TABLE production_service (
+    id serial NOT NULL PRIMARY KEY,
+    te_created_id bigint UNIQUE REFERENCES transaction_entry(id),
+    te_modified_id bigint UNIQUE REFERENCES transaction_entry(id),
+
+    quantity numeric(10, 2) CONSTRAINT positive_quantity CHECK  (quantity >= 0),
+    service_id bigint REFERENCES service(id),
+    order_id bigint REFERENCES production_order(id)
+);

Modified: stoqlib/trunk/stoqlib/database/tables.py
==============================================================================
--- stoqlib/trunk/stoqlib/database/tables.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/database/tables.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -106,6 +106,10 @@
      ('commission', ["CommissionSource", "Commission"]),
      ('transfer', ["TransferOrder", "TransferOrderItem"]),
      ('inventory', ["Inventory", "InventoryItem"]),
+     ('production', ["ProductionOrder",
+                     "ProductionItem",
+                     "ProductionMaterial",
+                     "ProductionService"]),
 
 ]
 

Modified: stoqlib/trunk/stoqlib/domain/exampledata.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/exampledata.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/exampledata.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -98,6 +98,19 @@
 def get_location(trans):
     return ExampleCreator.create(trans, 'CityLocation')
 
+def create_production_order(trans):
+    return ExampleCreator.create(trans, 'ProductionOrder')
+
+def create_production_item(trans):
+    return ExampleCreator.create(trans, 'ProductionItem')
+
+def create_production_material(trans):
+    return ExampleCreator.create(trans, 'ProductionMaterial')
+
+def create_production_service(trans):
+    return ExampleCreator.create(trans, 'ProductionService')
+
+
 class ExampleCreator(object):
     def __init__(self, trans):
         self.trans = trans
@@ -158,6 +171,10 @@
             'Service': self.create_service,
             'Till': self.create_till,
             'UserProfile': self.create_user_profile,
+            'ProductionOrder': self.create_production_order,
+            'ProductionItem': self.create_production_item,
+            'ProductionMaterial': self.create_production_material,
+            'ProductionService': self.create_production_service,
             }
         if isinstance(model_type, basestring):
             model_name = model_type
@@ -330,6 +347,41 @@
                             sellable=self.create_sellable(),
                             order=self.create_purchase_order())
 
+    def create_production_order(self):
+        from stoqlib.domain.production import ProductionOrder
+        return ProductionOrder(branch=self.create_branch(),
+                               responsible=self.create_employee(),
+                               description='production',
+                               connection=self.trans)
+
+    def create_production_item(self):
+        from stoqlib.domain.product import ProductComponent
+        from stoqlib.domain.production import ProductionItem
+        product = self.create_product(10)
+        component = self.create_product(5)
+        ProductComponent(product=product,
+                         component=component,
+                         connection=self.trans)
+        return ProductionItem(product=product,
+                              order=self.create_production_order(),
+                              connection=self.trans)
+
+    def create_production_material(self):
+        from stoqlib.domain.production import ProductionMaterial
+        production_item = self.create_production_item()
+        component = list(production_item.get_components())[0]
+        return ProductionMaterial(product=component.component,
+                                  order=self.create_production_order(),
+                                  connection=self.trans)
+
+
+    def create_production_service(self):
+        from stoqlib.domain.production import ProductionService
+        service = self.create_service()
+        return ProductionService(service=service,
+                                 order=self.create_production_order(),
+                                 connection=self.trans)
+
     def create_cfop_data(self):
         from stoqlib.domain.fiscal import CfopData
         return CfopData(connection=self.trans, code=u'123',

Modified: stoqlib/trunk/stoqlib/domain/product.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/product.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/product.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -235,11 +235,17 @@
     def get_components(self):
         """Returns the products which are our components.
 
-        @returns: a sequence of Product instances
+        @returns: a sequence of ProductComponent instances
         """
-        for component in ProductComponent.selectBy(
-            product=self, connection=self.get_connection()):
-            yield component
+        return ProductComponent.selectBy(product=self,
+                                         connection=self.get_connection())
+
+    def has_components(self):
+        """Returns if this product has components or not.
+
+        @returns: True if this product has components, False otherwise.
+        """
+        return self.get_components().count() > 0
 
     def get_production_cost(self):
         """ Return the production cost of a Product. The production cost

Modified: stoqlib/trunk/stoqlib/domain/production.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/production.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/production.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -28,9 +28,10 @@
 
 from zope.interface import implements
 
-from stoqlib.database.orm import UnicodeCol, ForeignKey, DateTimeCol, IntCol
+from stoqlib.database.orm import (UnicodeCol, ForeignKey, DateTimeCol, IntCol,
+                                  DecimalCol)
 from stoqlib.domain.base import Domain
-from stoqlib.domain.interfaces import IContainer, IDescribable
+from stoqlib.domain.interfaces import IContainer, IDescribable, IStorable
 from stoqlib.lib.translation import stoqlib_gettext
 
 _ = stoqlib_gettext
@@ -66,25 +67,69 @@
 
     status = IntCol(default=ORDER_OPENED)
     open_date = DateTimeCol(default=datetime.datetime.now)
+    expected_start_date = DateTimeCol(default=None)
+    start_date = DateTimeCol(default=None)
     close_date = DateTimeCol(default=None)
     description = UnicodeCol(default='')
-    responsible = ForeignKey('PersonAdaptToEmployee')
+    responsible = ForeignKey('PersonAdaptToEmployee', default=None)
     branch = ForeignKey('PersonAdaptToBranch')
 
     #
     # IContainer implmentation
     #
 
-    #TODO: when implement ProductionItem.
-
     def get_items(self):
-        return []
+        return ProductionItem.selectBy(order=self,
+                                       connection=self.get_connection())
 
     def add_item(self, sellable, quantity=Decimal(1)):
-        return
+        return ProductionItem(order=self, product=sellable.product,
+                              quantity=quantity,
+                              connection=self.get_connection())
 
     def remove_item(self, item):
-        return
+        if item.order is not self:
+            raise ValueError('Argument item must have an order attribute '
+                             'associated with the current production '
+                             'order instance.')
+        ProductionItem.delete(item.id, connection=self.get_connection())
+
+    #
+    # Public API
+    #
+
+    def get_service_items(self):
+        """Returns all the services needed by this production.
+
+        @returns: a sequence of L{ProductionService} instances.
+        """
+        return ProductionService.selectBy(order=self,
+                                          connection=self.get_connection())
+
+    def get_material_items(self):
+        """Returns all the material needed by this production.
+
+        @returns: a sequence of L{ProductionMaterial} instances.
+        """
+        return ProductionMaterial.selectBy(order=self,
+                                           connection=self.get_connection())
+
+    def start_production(self):
+        """Start the production by allocating all the material needed.
+        """
+        assert self.status in [ProductionOrder.ORDER_OPENED,
+                               ProductionOrder.ORDER_WAITING]
+
+        for material in self.get_material_items():
+            material.allocate()
+
+        self.start_date = datetime.date.today()
+        self.status = ProductionOrder.ORDER_PRODUCING
+
+    def set_production_waiting(self):
+        assert self.status == ProductionOrder.ORDER_OPENED
+
+        self.status = ProductionOrder.ORDER_WAITING
 
     #
     # IDescribable implementation
@@ -92,3 +137,122 @@
 
     def get_description(self):
         return self.description
+
+
+class ProductionItem(Domain):
+    """Production Item object implementation.
+
+    @ivar order: The L{ProductionOrder} of this item.
+    @ivar product: The product that will be manufactured.
+    @ivar quantity: The product's quantity that will be manufactured.
+    """
+    implements(IDescribable)
+
+    quantity = DecimalCol(default=1)
+    order = ForeignKey('ProductionOrder')
+    product = ForeignKey('Product')
+
+    #
+    # IDescribable Implementation
+    #
+
+    def get_description(self):
+        return self.product.sellable.get_description()
+
+    #
+    # Public API
+    #
+
+    def get_unit_description(self):
+        return self.product.sellable.get_unit_description()
+
+    def get_components(self):
+        return self.product.get_components()
+
+
+class ProductionMaterial(Domain):
+    """Production Material object implementation.
+
+    @ivar order: The L{ProductionOrder} that will consume this material.
+    @ivar product: The product that will be consumed.
+    @ivar needed: The quantity needed of this material.
+    @ivar consumed: The quantity already used of this material.
+    @ivar lost: The quantity lost of this material.
+    @ivar to_purchase: The quantity to purchase of this material.
+    @ivar to_make: The quantity to manufacture of this material.
+    """
+    implements(IDescribable)
+
+    needed = DecimalCol(default=1)
+    allocated = DecimalCol(default=0)
+    consumed = DecimalCol(default=0)
+    lost = DecimalCol(default=0)
+    to_purchase = DecimalCol(default=0)
+    to_make = DecimalCol(default=0)
+    order = ForeignKey('ProductionOrder')
+    product = ForeignKey('Product')
+
+    #
+    # Public API
+    #
+
+    def allocate(self):
+        """Allocates the needed quantity of this material by decreasing the
+        stock quantity. If the available quantity is not enough, then it will
+        allocate all the stock available.
+        """
+        stock = self.get_stock_quantity()
+        storable = IStorable(self.product, None)
+        assert storable is not None
+
+        if stock > self.needed:
+            quantity = self.needed
+        else:
+            quantity = stock
+
+        if quantity > 0:
+            self.allocated = quantity
+            storable.decrease_stock(quantity, self.order.branch)
+
+    #
+    # IDescribable Implementation
+    #
+
+    def get_description(self):
+        return self.product.sellable.get_description()
+
+    # Accessors
+
+    def get_unit_description(self):
+        return self.product.sellable.get_unit_description()
+
+    def get_stock_quantity(self):
+        storable = IStorable(self.product, None)
+        assert storable is not None
+        return storable.get_full_balance(self.order.branch)
+
+
+class ProductionService(Domain):
+    """Production Service object implementation.
+
+    @ivar order: The L{ProductionOrder} of this service.
+    @ivar service: The service that will be used by the production.
+    @ivar quantity: The service's quantity.
+    """
+    implements(IDescribable)
+
+    quantity = DecimalCol(default=1)
+    order = ForeignKey('ProductionOrder')
+    service = ForeignKey('Service')
+
+    #
+    # IDescribable Implementation
+    #
+
+    def get_description(self):
+        return self.service.sellable.get_description()
+
+    # Accessors
+
+    def get_unit_description(self):
+        return self.service.sellable.get_unit_description()

Modified: stoqlib/trunk/stoqlib/domain/service.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/service.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/service.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -101,3 +101,7 @@
 
     def get_unit(self):
         return self.unit or u""
+
+    @property
+    def sellable(self):
+        return Sellable.get(self.id, connection=self.get_connection())

Modified: stoqlib/trunk/stoqlib/domain/test/test_product.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/test/test_product.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/test/test_product.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -87,6 +87,15 @@
         self.assertEqual(list(self.product.get_components()),
                         components)
 
+    def testHasComponents(self):
+        self.assertFalse(self.product.has_components())
+
+        component = self.create_product()
+        ProductComponent(product=self.product,
+                         component=component,
+                         connection=self.trans)
+        self.assertTrue(self.product.has_components())
+
     def testGetProductionCost(self):
         product = self.create_product()
         sellable = product.sellable

Added: stoqlib/trunk/stoqlib/domain/test/test_production.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/stoqlib/domain/test/test_production.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+##
+## Copyright (C) 2009 Async Open Source <http://www.async.com.br>
+## All rights reserved
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation; either version 2 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
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., or visit: http://www.gnu.org/.
+##
+## Author(s):   George Y. Kussumoto     <george at async.com.br>
+##
+""" This module test all class in stoqlib/domain/production.py """
+
+
+from stoqlib.domain.interfaces import IStorable
+from stoqlib.domain.production import ProductionOrder
+from stoqlib.domain.test.domaintest import DomainTest
+
+
+class TestProductionOrder(DomainTest):
+
+    def testGetServiceItems(self):
+        order = self.create_production_order()
+        self.assertEqual(list(order.get_service_items()), [])
+
+        service_item = self.create_production_service()
+        service_item.order = order
+        self.assertEqual(list(order.get_service_items()), [service_item])
+
+    def testGetMaterialItems(self):
+        order = self.create_production_order()
+        self.assertEqual(list(order.get_material_items()), [])
+
+        material_item = self.create_production_material()
+        material_item.order = order
+        self.assertEqual(list(order.get_material_items()), [material_item])
+
+    def testStartProduction(self):
+        order = self.create_production_order()
+        self.assertEqual(order.status, ProductionOrder.ORDER_OPENED)
+        order.start_production()
+        self.assertEqual(order.status, ProductionOrder.ORDER_PRODUCING)
+
+    def testSetProductionWaiting(self):
+        order = self.create_production_order()
+        self.assertEqual(order.status, ProductionOrder.ORDER_OPENED)
+        order.set_production_waiting()
+        self.assertEqual(order.status, ProductionOrder.ORDER_WAITING)
+
+
+class TestProductionMaterial(DomainTest):
+
+    def testAllocate(self):
+        material = self.create_production_material()
+        branch = material.order.branch
+        product = material.product
+        storable = IStorable(product, None)
+        if storable is None:
+            storable = product.addFacet(IStorable, connection=self.trans)
+        storable.increase_stock(10, branch)
+        material.needed = 10
+        stock_qty = material.get_stock_quantity()
+        self.assertEqual(stock_qty, 10)
+
+        material.allocate()
+        stock_qty = material.get_stock_quantity()
+        self.assertEqual(stock_qty, 0)
+        material.allocate()
+        self.assertEqual(stock_qty, 0)

Modified: stoqlib/trunk/stoqlib/domain/views.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/views.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/domain/views.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -136,6 +136,10 @@
     clause = AND(ProductFullStockView.clause,
                  ProductComponent.q.productID == Product.q.id,)
 
+    @property
+    def sellable(self):
+        return Sellable.get(self.id, connection=self.get_connection())
+
 
 class ProductWithStockView(ProductFullStockView):
     """Stores information about products, since product has a purchase or sale.

Added: stoqlib/trunk/stoqlib/gui/editors/productioneditor.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/stoqlib/gui/editors/productioneditor.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+##
+## Copyright (C) 2009 Async Open Source <http://www.async.com.br>
+## All rights reserved
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation; either version 2 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
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., or visit: http://www.gnu.org/.
+##
+## Author(s):   George Kussumoto        <george at async.com.br>
+##
+""" Production editors """
+
+
+import sys
+
+import gtk
+
+from kiwi.datatypes import ValidationError
+
+from stoqlib.gui.editors.baseeditor import BaseEditor
+from stoqlib.domain.production import (ProductionItem, ProductionMaterial,
+                                       ProductionService)
+from stoqlib.lib.defaults import DECIMAL_PRECISION
+from stoqlib.lib.translation import stoqlib_gettext
+
+_ = stoqlib_gettext
+
+
+class ProductionItemEditor(BaseEditor):
+    gladefile = 'ProductionItemEditor'
+    model_type = ProductionItem
+    size = (-1, 150)
+    model_name = _(u'Production Item')
+    proxy_widgets = ['description', 'quantity', 'unit_description',]
+
+    def setup_location_widgets(self):
+        location = self.model.product.location
+        if location:
+            self.location.set_text(location)
+        else:
+            self.location.hide()
+            self.location_content.hide()
+
+    def setup_editor_widgets(self):
+        self.order_number.set_text("%04d" %  self.model.order.id)
+        self.quantity.set_adjustment(gtk.Adjustment(lower=0, upper=sys.maxint,
+                                                    step_incr=1))
+        self.quantity.set_digits(DECIMAL_PRECISION)
+
+    def setup_proxies(self):
+        self.setup_editor_widgets()
+        self.setup_location_widgets()
+        self.proxy = self.add_proxy(
+            self.model, ProductionItemEditor.proxy_widgets)
+
+    #
+    # Kiwi callbacks
+    #
+
+    def on_quantity__validate(self, widget, value):
+        if not value or value <= 0:
+            return ValidationError(_(u'This quantity should be positive.'))
+
+
+class ProductionServiceEditor(ProductionItemEditor):
+    model_type = ProductionService
+    model_name = _(u'Production Service')
+
+    def setup_proxies(self):
+        self.setup_editor_widgets()
+        self.location_content.hide()
+        self.proxy = self.add_proxy(
+            self.model, ProductionServiceEditor.proxy_widgets)
+
+
+class ProductionMaterialEditor(ProductionItemEditor):
+    model_type = ProductionMaterial
+    model_name = _(u'Production Material Item')
+    proxy_widgets = ['description',]
+
+    def setup_proxies(self):
+        self.setup_editor_widgets()
+        self.setup_location_widgets()
+        self.proxy = self.add_proxy(
+            self.model, ProductionMaterialEditor.proxy_widgets)
+
+        self._has_components = self.model.product.has_components()
+        if self._has_components:
+            proxy_field = 'to_make'
+            self.quantity_lbl.set_text(_(u'Quantity to make:'))
+        else:
+            proxy_field = 'to_purchase'
+            self.quantity_lbl.set_text(_(u'Quantity to purchase:'))
+
+        self.quantity.set_property('model-attribute', proxy_field)
+        self.proxy.add_widget(proxy_field, self.quantity)
+
+    #
+    # Kiwi Callbacks
+    #
+
+    def on_quantity__validate(self, widget, value):
+        if value and value < 0:
+            return ValidationError(_(u'This quantity should be positive.'))

Modified: stoqlib/trunk/stoqlib/gui/wizards/abstractwizard.py
==============================================================================
--- stoqlib/trunk/stoqlib/gui/wizards/abstractwizard.py	Thu Jul 30 13:46:17 2009	(r8187)
+++ stoqlib/trunk/stoqlib/gui/wizards/abstractwizard.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -82,6 +82,7 @@
     table = Sellable
     item_table = None
     summary_label_text = None
+    summary_label_column = 'total'
 
     def __init__(self, wizard, previous, conn, model):
         WizardEditorStep.__init__(self, conn, wizard, model, previous)
@@ -209,7 +210,8 @@
 
     def _setup_summary(self):
         # FIXME: Move this into AdditionListSlave
-        self.summary = SummaryLabel(klist=self.slave.klist, column='total',
+        self.summary = SummaryLabel(klist=self.slave.klist,
+                                    column=self.summary_label_column,
                                     label=self.summary_label_text,
                                     value_format='<b>%s</b>')
         self.summary.show()

Added: stoqlib/trunk/stoqlib/gui/wizards/productionwizard.py
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stoqlib/trunk/stoqlib/gui/wizards/productionwizard.py	Fri Jul 31 11:01:36 2009	(r8188)
@@ -0,0 +1,523 @@
+# -*- coding: utf-8 -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+##
+## Copyright (C) 2009 Async Open Source <http://www.async.com.br>
+## All rights reserved
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation; either version 2 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
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., or visit: http://www.gnu.org/.
+##
+## Author(s):   George Kussumoto    <george at async.com.br>
+##
+""" Production wizard definition """
+
+import datetime
+from decimal import Decimal
+
+import pango
+
+from kiwi.datatypes import ValidationError, currency
+from kiwi.ui.widgets.list import Column, ColoredColumn
+
+from stoqlib.database.runtime import get_current_branch
+from stoqlib.domain.interfaces import IBranch, IStorable
+from stoqlib.domain.person import Person, PersonAdaptToEmployee
+from stoqlib.domain.production import (ProductionOrder, ProductionItem,
+                                       ProductionMaterial, ProductionService)
+from stoqlib.domain.service import ServiceView
+from stoqlib.domain.sellable import Sellable
+from stoqlib.domain.views import ProductComponentView
+from stoqlib.lib.translation import stoqlib_gettext
+from stoqlib.lib.parameters import sysparam
+from stoqlib.lib.validators import format_quantity
+from stoqlib.gui.base.dialogs import run_dialog
+from stoqlib.gui.base.wizards import WizardEditorStep, BaseWizard
+from stoqlib.gui.editors.productioneditor import (ProductionItemEditor,
+                                                  ProductionMaterialEditor,
+                                                  ProductionServiceEditor)
+from stoqlib.gui.wizards.abstractwizard import SellableItemStep
+
+_ = stoqlib_gettext
+
+
+#
+# Wizard Steps
+#
+
+
+class OpenProductionOrderStep(WizardEditorStep):
+    gladefile = 'OpenProductionOrderStep'
+    model_type = ProductionOrder
+    proxy_widgets = ['open_date',
+                     'expected_start_date',
+                     'order_number',
+                     'branch',
+                     'responsible',
+                     'description',]
+
+    def __init__(self, conn, wizard, model):
+        WizardEditorStep.__init__(self, conn, wizard, model)
+
+    def _fill_branch_combo(self):
+        # FIXME: Implement and use IDescribable on PersonAdaptToBranch
+        table = Person.getAdapterClass(IBranch)
+        branches = table.get_active_branches(self.conn)
+        items = [(s.person.name, s) for s in branches]
+        self.branch.prefill(sorted(items))
+
+    def _fill_responsible_combo(self):
+        employees = PersonAdaptToEmployee.selectBy(
+            status=PersonAdaptToEmployee.STATUS_NORMAL,
+            connection=self.conn)
+        self.responsible.prefill([(e.person.name, e) for e in employees])
+
+    def _setup_widgets(self):
+        self._fill_branch_combo()
+        self._fill_responsible_combo()
+        self.open_date.set_sensitive(False)
+
+    #
+    # WizardStep hooks
+    #
+
+    def post_init(self):
+        self.description.grab_focus()
+        self.table.set_focus_chain([self.branch, self.expected_start_date,
+                                    self.responsible, self.description])
+        self.register_validate_function(self.wizard.refresh_next)
+        self.force_validation()
+
+    def next_step(self):
+        return ProductionServiceStep(self.wizard, self, self.conn, self.model)
+
+    def has_previous_step(self):
+        return False
+
+    def setup_proxies(self):
+        self._setup_widgets()
+        self.proxy = self.add_proxy(
+            self.model, OpenProductionOrderStep.proxy_widgets)
+        self.proxy.update('order_number', u'%04d' % self.model.id)
+        # suggests a responsible for the production order
+        self.responsible.select_item_by_position(0)
+
+    #
+    # Kiwi Callbacks
+    #
+
+    def on_expected_start_date__validate(self, widget, value):
+        today = datetime.date.today()
+        if value and value < today:
+            return ValidationError(
+                _(u'Expected start date should be a future date.'))
+
+
+class ProductionServiceStep(SellableItemStep):
+    model_type = ProductionOrder
+    item_table = ProductionService
+    summary_label_text = "<b>%s</b>" % _('Total:')
+    summary_label_column = 'quantity'
+
+    #
+    # Helper methods
+    #
+
+    def setup_sellable_entry(self):
+        max_results = sysparam(self.conn).MAX_SEARCH_RESULTS
+        service_view_items = ServiceView.select(
+            ServiceView.q.status == Sellable.STATUS_AVAILABLE,
+            connection=self.conn,
+            limit=max_results)
+        delivery = sysparam(self.conn).DELIVERY_SERVICE
+        self.sellable.prefill([(s.description, s.sellable)
+            for s in service_view_items if s.id != delivery.id])
+
+    def setup_slaves(self):
+        SellableItemStep.setup_slaves(self)
+        self.item_lbl.set_text(_(u'Services:'))
+        self.hide_add_button()
+        self.product_button.hide()
+        self.cost_label.hide()
+        self.cost.hide()
+
+        self.quantity.connect('validate', self._on_quantity__validate)
+
+    def _get_production_service_by_sellable(self, sellable):
+        service = sellable.service
+        for item in self.slave.klist:
+            if item.service is service:
+                return item
+
+    #
+    # SellableItemStep virtual methods
+    #
+
+    def validate(self, value):
+        # This step is optional
+        return True
+
+    def get_order_item(self, sellable, cost, quantity):
+        item = self._get_production_service_by_sellable(sellable)
+        if item is None:
+            return ProductionService(service=sellable.service,
+                                     quantity=quantity,
+                                     order=self.model,
+                                     connection=self.conn)
+        item.quantity += quantity
+        return item
+
+    def get_saved_items(self):
+        return list(self.model.get_service_items())
+
+    def get_columns(self):
+        return [
+            Column('service.sellable.code', title=_('Code'), data_type=str),
+            Column('service.sellable.category_description', title=_('Category'),
+                    data_type=str, expand=True),
+            Column('service.sellable.description', title=_('Description'),
+                    data_type=str, expand=True, sorted=True),
+            Column('quantity', title=_('Quantity'), data_type=Decimal,
+                    format_func=format_quantity),
+            Column('service.sellable.unit_description',title=_('Unit'),
+                    data_type=str),
+            Column('service.sellable.cost', title=_('Cost'),
+                    data_type=currency),]
+
+    #
+    # WizardStep hooks
+    #
+
+    def post_init(self):
+        SellableItemStep.post_init(self)
+        self.slave.set_editor(ProductionServiceEditor)
+
+    def next_step(self):
+        return ProductionItemStep(self.wizard, self, self.conn, self.model)
+
+    #
+    # Callbacks
+    #
+
+    def _on_quantity__validate(self, widget, value):
+        if not value or value <= Decimal(0):
+            return ValidationError(_(u'Quantity must be greater than zero'))
+
+
+class ProductionItemStep(SellableItemStep):
+    """ Wizard step for production items selection """
+    model_type = ProductionOrder
+    item_table = ProductionItem
+    summary_label_text = "<b>%s</b>" % _('Total:')
+    summary_label_column = 'quantity'
+
+    #
+    # Helper methods
+    #
+
+    def setup_sellable_entry(self):
+        max_results = sysparam(self.conn).MAX_SEARCH_RESULTS
+        composable_items = ProductComponentView.select(connection=self.conn,
+                                                       limit=max_results)
+        self.sellable.prefill(
+            [(c.description, c.sellable) for c in composable_items])
+
+    def setup_slaves(self):
+        SellableItemStep.setup_slaves(self)
+        self.hide_add_button()
+        self.product_button.hide()
+        self.cost_label.hide()
+        self.cost.hide()
+
+        self.quantity.connect('validate', self._on_quantity__validate)
+
+    def _get_production_item_by_sellable(self, sellable):
+        product = sellable.product
+        for item in self.slave.klist:
+            if item.product is product:
+                return item
+
+    #
+    # SellableItemStep virtual methods
+    #
+
+    def get_order_item(self, sellable, cost, quantity):
+        item = self._get_production_item_by_sellable(sellable)
+        if item is None:
+            return self.model.add_item(sellable, quantity)
+
+        item.quantity += quantity
+        return item
+
+    def get_saved_items(self):
+        return list(self.model.get_items())
+
+    def get_columns(self):
+        return [
+            Column('product.sellable.code', title=_('Code'), data_type=str),
+            Column('product.sellable.category_description', title=_('Category'),
+                    data_type=str, expand=True),
+            Column('product.sellable.description', title=_('Description'),
+                    data_type=str, expand=True, sorted=True),
+            Column('quantity', title=_('Quantity'), data_type=Decimal,
+                    format_func=format_quantity),
+            Column('product.sellable.unit_description',title=_('Unit'),
+                    data_type=str),]
+
+    #
+    # WizardStep hooks
+    #
+
+    def post_init(self):
+        SellableItemStep.post_init(self)
+        self.slave.set_editor(ProductionItemEditor)
+
+    def next_step(self):
+        return FinishOpenProductionOrderStep(self.wizard, self, self.conn,
+                                             self.model)
+
+    #
+    # Callbacks
+    #
+
+    def _on_quantity__validate(self, widget, value):
+        if not value or value <= Decimal(0):
+            return ValidationError(_(u'Quantity must be greater than zero'))
+
+
+#XXX: This is just a workaround to avoid the zillions of queries
+#     when handling production items and materials.
+class _TemporaryMaterial(object):
+    def __init__(self, production, component, conn):
+        storable = IStorable(component, None)
+        if storable is not None:
+            self.stock_quantity =  storable.get_full_balance(production.branch)
+        else:
+            self.stock_quantity = 0
+
+        sellable = component.sellable
+        self.code = sellable.code
+        self.description = sellable.get_description()
+        self.category_description = sellable.get_description()
+        self.unit_description = sellable.get_unit_description()
+        self.product = component
+        self.needed = 0
+        self.to_purchase = 0
+        self.to_make = 0
+        self.order = production
+        self._material = None
+        self._conn = conn
+
+    @property
+    def material(self):
+        if self._material is None:
+            # At this point, the needed quantity have already been updated.
+            assert self.needed > 0
+            material = ProductionMaterial.selectOneBy(order=self.order,
+                                                      product=self.product,
+                                                      connection=self._conn)
+            if material is not None:
+                self._material = material
+                self._material.needed = self.needed
+            else:
+                self._material = ProductionMaterial(
+                                                needed=self.needed,
+                                                to_purchase=self.to_purchase,
+                                                to_make=self.to_make,
+                                                order=self.order,
+                                                product=self.product,
+                                                connection=self._conn)
+        return self._material
+
+    def create(self):
+        return self.material
+
+    def sync(self):
+        #assert self._material is not None
+        self.to_purchase = self.material.to_purchase
+        self.to_make = self.material.to_make
+
+    def add_quantity(self, quantity):
+        assert quantity > 0
+        self.needed += quantity
+        missing_quantity = self.needed - self.stock_quantity
+        if missing_quantity > 0:
+            if self.product.has_components():
+                self.to_make = missing_quantity
+            else:
+                self.to_purchase = missing_quantity
+        if self._material is not None:
+            self._material.needed = self.needed
+            self._material.to_make = self.to_make
+            self._material.to_purchase = self.to_purchase
+
+
+class FinishOpenProductionOrderStep(WizardEditorStep):
+    gladefile = 'FinishOpenProductionOrderStep'
+    model_type = ProductionOrder
+    proxy_widgets = []
+
+    def __init__(self, wizard, previous, conn, model):
+        WizardEditorStep.__init__(self, conn, wizard, model, previous)
+        self._setup_widgets()
+
+    def _add_materials(self, production_item):
+        self._materials_objects = []
+        for product_component in production_item.get_components():
+            material = self._get_or_create_material(product_component)
+            quantity = product_component.quantity * production_item.quantity
+            material.add_quantity(quantity)
+            material.sync()
+
+            if material not in self.materials:
+                self.materials.append(material)
+            else:
+                self.materials.update(material)
+
+    def _get_or_create_material(self, product_component):
+        component = product_component.component
+        for material in self.materials:
+            if material.product is component:
+                return material
+        return _TemporaryMaterial(self.model, component, self.conn)
+
+    def _edit_production_material(self):
+        material = self.materials.get_selected()
+        assert material is not None
+
+        retval = run_dialog(ProductionMaterialEditor, self, self.conn,
+                            material.material)
+        if retval:
+            material.sync()
+            self.materials.update(material)
+
+    def _setup_widgets(self):
+        self.edit_button.set_sensitive(False)
+        self.materials.set_columns(self._get_columns())
+        for production_item in self.model.get_items():
+            self._add_materials(production_item)
+
+    def _get_columns(self):
+        return [
+            Column('code', title=_('Code'), data_type=str),
+            Column('category_description', title=_('Category'),
+                    data_type=str, expand=True, ellipsize=pango.ELLIPSIZE_END),
+            Column('description', title=_('Description'), data_type=str,
+                    expand=True, ellipsize=pango.ELLIPSIZE_END, sorted=True),
+            Column('unit_description',title=_('Unit'),
+                    data_type=str),
+            Column('needed', title=_('Needed'), data_type=Decimal,
+                    format_func=format_quantity),
+            Column('stock_quantity', title=_('In Stock'), data_type=Decimal,
+                    format_func=format_quantity),
+            ColoredColumn('to_purchase', title=_('To Purchase'),
+                          data_type=Decimal, format_func=format_quantity,
+                          use_data_model=True, color='red',
+                          data_func=self._colorize_to_purchase_col),
+            ColoredColumn('to_make', title=_('To Make'), data_type=Decimal,
+                          format_func=format_quantity, use_data_model=True,
+                          color='red', data_func=self._colorize_to_make_col),]
+
+    #XXX: Some duplication here, since the columns will never be both red.
+
+    def _colorize_to_purchase_col(self, material):
+        if material.product.has_components():
+            return
+        stock_qty = material.stock_quantity
+        if material.to_purchase + stock_qty - material.needed < 0:
+           return True
+        return False
+
+    def _colorize_to_make_col(self, material):
+        if not material.product.has_components():
+            return
+        stock_qty = material.stock_quantity
+        if material.to_make + stock_qty - material.needed < 0:
+           return True
+        return False
+
+    #
+    # WizardStep hooks
+    #
+
+    def has_next_step(self):
+        return False
+
+    def post_init(self):
+        self.register_validate_function(self.wizard.refresh_next)
+        self.force_validation()
+
+    def setup_proxies(self):
+        self.proxy = self.add_proxy(self.model, self.proxy_widgets)
+
+    def validate_step(self):
+        # Used as a hook for the finish button
+        for material in self.materials:
+            material.create()
+
+        if self.start_production_check.get_active():
+            self.model.start_production()
+        elif self.model.status != ProductionOrder.ORDER_WAITING:
+            for material in self.materials:
+                if material.to_purchase > 0 or material.to_make > 0:
+                    self.model.set_production_waiting()
+                    break
+        return True
+
+    #
+    # Kiwi Callbacks
+    #
+
+    def on_materials__selection_changed(self, widget, material):
+        self.edit_button.set_sensitive(bool(material))
+
+    def on_materials__double_click(self, widget, material):
+        self._edit_production_material()
+
+    def on_edit_button__clicked(self, widget):
+        self._edit_production_material()
+
+
+#
+# Main wizard
+#
+
+
+class ProductionWizard(BaseWizard):
+    size = (775, 400)
+
+    def __init__(self, conn, model=None, edit_mode=False):
+        title = self._get_title(model)
+        model = model or self._create_model(conn)
+        first_step = OpenProductionOrderStep(conn, self, model)
+        BaseWizard.__init__(self, conn, first_step, model, title=title,
+                            edit_mode=edit_mode)
+
+    def _get_title(self, model=None):
+        if not model:
+            return _(u'New Production')
+        return _(u'Edit Production')
+
+    def _create_model(self, conn):
+        branch = get_current_branch(conn)
+        return ProductionOrder(branch=branch,
+                               connection=conn)
+
+    #
+    # WizardStep hooks
+    #
+
+    def finish(self):
+        self.retval = self.model
+        self.close()


More information about the POS-commit mailing list