Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py	(revision 9265)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/batching.py	(revision 9266)
@@ -20,9 +20,16 @@
 """
 import grok
-from hurry.workflow.interfaces import IWorkflowState
+from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
 from zope.interface import Interface
-from waeup.kofa.interfaces import IBatchProcessor, IGNORE_MARKER, IObjectHistory
+from waeup.kofa.interfaces import (
+    IBatchProcessor, IGNORE_MARKER, IObjectHistory, IObjectConverter)
 from waeup.kofa.utils.batching import BatchProcessor
 from waeup.kofa.accesscodes.interfaces import IAccessCodeBatch, IAccessCode
+from waeup.kofa.accesscodes.workflow import INITIALIZED, USED, DISABLED
+
+IMPORTABLE_TRANSITIONS = (
+    'init', 'use', 'disable_used', 'disable_used', 'reeanble')
+
+IMPORTABLE_STATES = (INITIALIZED, USED, DISABLED)
 
 class AccessCodeBatchProcessor(BatchProcessor):
@@ -98,11 +105,10 @@
                     self.location_fields + [
                         'random_num', 'owner', 'cost',
-                        'state', 'batch_serial',
-                        'history',]
+                        'state', 'batch_serial',]
                     )))
 
     @property
     def required_fields(self):
-        return ['random_num', 'state', 'batch_serial', 'history',]
+        return ['random_num', 'batch_serial', 'history',]
 
     def parentsExist(self, row, site):
@@ -133,4 +139,44 @@
         parent[row['representation']] = obj
         return
+
+    def checkConversion(self, row, mode='create'):
+        """Validates all values in row.
+        """
+        converter = IObjectConverter(self.iface)
+        errs, inv_errs, conv_dict =  converter.fromStringDict(
+            row, self.factory_name, mode=mode)
+        if row.has_key('transition') and row.has_key('state'):
+            if row['transition'] not in (IGNORE_MARKER, '') and \
+                row['state'] not in (IGNORE_MARKER, ''):
+                errs.append(('workflow','not allowed'))
+                return errs, inv_errs, conv_dict
+        if row.has_key('transition'):
+            if row['transition'] not in IMPORTABLE_TRANSITIONS:
+                if row['transition'] not in (IGNORE_MARKER, ''):
+                    errs.append(('transition','not allowed'))
+        if row.has_key('state'):
+            if row['state'] not in IMPORTABLE_STATES:
+                if row['state'] not in (IGNORE_MARKER, ''):
+                    errs.append(('state','not allowed'))
+                else:
+                    # State is an attribute of AccessCode and must not
+                    # be changed if empty.
+                    conv_dict['state'] = IGNORE_MARKER
+        return errs, inv_errs, conv_dict
+
+    def checkUpdateRequirements(self, obj, row, site):
+        """Checks requirements the object must fulfill when being updated.
+
+        This method is not used in case of deleting or adding objects.
+
+        Returns error messages as strings in case of requirement
+        problems.
+        """
+        transition = row.get('transition', IGNORE_MARKER)
+        if transition not in (IGNORE_MARKER, ''):
+            allowed_transitions = IWorkflowInfo(obj).getManualTransitionIds()
+            if transition not in allowed_transitions:
+                return 'Transition not allowed.'
+        return None
 
     def updateEntry(self, obj, row, site):
@@ -147,19 +193,23 @@
                 IWorkflowState(obj).setState(value)
                 items_changed += ('%s=%s, ' % ('state', state))
+                msg = "state '%s' set" % state
+                IObjectHistory(obj).addMessage(msg)
             row.pop('state')
-        # Update history
-        if row.has_key('history'):
-            history = row.get('history', IGNORE_MARKER)
-            if history not in (IGNORE_MARKER, ''):
-                values = row['history'].split('||')
-                for value in values:
-                    IObjectHistory(obj).addMessage(value)
-                items_changed += ('%s=%s, ' % ('history', history))
-            row.pop('history')
+        # Fire transition
+        if row.has_key('transition'):
+            transition = row.get('transition', IGNORE_MARKER)
+            if transition not in (IGNORE_MARKER, ''):
+                value = row['transition']
+                IWorkflowInfo(obj).fireTransition(value)
+                items_changed += ('%s=%s, ' % ('transition', transition))
+            row.pop('transition')
 
         # In import files we can use the hash symbol at the end of a
         # random_num string to avoid annoying automatic number transformation
         # by Excel or Calc
-        row['random_num'] = row['random_num'].strip('#')
+        if row.has_key('random_num'):
+            random_num = row.get('random_num', IGNORE_MARKER)
+            if random_num not in (IGNORE_MARKER, ''):
+                row['random_num'] = random_num.strip('#')
 
         items_changed += super(AccessCodeProcessor, self).updateEntry(
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py	(revision 9265)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/interfaces.py	(revision 9266)
@@ -43,4 +43,5 @@
     state = schema.TextLine(
         title = _(u'Workflow state'),
+        required = False,
         )
     representation = schema.TextLine(
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data.csv	(revision 9265)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data.csv	(revision 9266)
@@ -1,4 +1,9 @@
-batch_num,batch_prefix,batch_serial,cost,history,owner,random_num,representation,state
-1,CLR,33,100.0,2012-07-11 14:51:56 UTC - initialized by Manager||2012-07-11 14:55:42 UTC - invalidated by Manager,K1000009,1625368961#,CLR-1-1625368961,used
-1,CLR,2,100.0,2012-05-12 16:32:29 UTC - initialized by Manager,,5468453598#,CLR-1-5468453598,initialized
-1,HOS,731,0.0,2012-09-19 08:59:28 UTC - initialized by Manager,,0007044547#,HOS-1-0007044547,initialized
+batch_num,batch_prefix,batch_serial,cost,history,owner,random_num,representation,state,transition
+1,CLR,33,100.0,anything,K1000009,1625368961#,CLR-1-1625368961,used,
+1,CLR,2,100.0,anything,,5468453598#,CLR-1-5468453598,initialized,
+1,HOS,456,0.0,anything,,98769876#,HOS-1-98769876,,use
+1,HOS,456,0.0,anything,,76254765#,HOS-1-76254765,,
+1,HOS,23,0.0,anything,,0007044547#,HOS-1-0007044547,nonsense,
+1,HOS,731,0.0,anything,,555#,HOS-1-555,used,disable_unused
+1,HOS,456,0.0,anything,,666#,HOS-1-666,,nonsense
+
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data_update.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data_update.csv	(revision 9266)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_ac_data_update.csv	(revision 9266)
@@ -0,0 +1,3 @@
+batch_num,batch_prefix,representation,state,transition
+1,CLR,CLR-1-1625368961,disabled,
+1,HOS,HOS-1-98769876,,disable_used
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv	(revision 9265)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/sample_batch_data.csv	(revision 9266)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py	(revision 9265)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_batching.py	(revision 9266)
@@ -54,4 +54,11 @@
     '\n')[0].split(',')
 
+AC_SAMPLE_DATA_UPDATE = open(
+    os.path.join(os.path.dirname(__file__), 'sample_ac_data_update.csv'),
+    'rb').read()
+
+AC_HEADER_FIELDS_UPDATE = AC_SAMPLE_DATA_UPDATE.split(
+    '\n')[0].split(',')
+
 class ACFullSetup(FunctionalTestCase):
 
@@ -104,6 +111,6 @@
         self.assertTrue(
             'system - AccessCodeBatch Processor - CLR-1 - '
-            'Batch updated: num=1, creator=system, entry_num=3, '
-            'creation_date=2012-04-28 07:28:48.719026+00:00, '
+            'Batch updated: num=1, creator=system, used_num=1, '
+            'entry_num=3, creation_date=2012-04-28 07:28:48.719026+00:00, '
             'prefix=CLR, cost=0.0, disabled_num=0'
             in logcontent)
@@ -115,5 +122,5 @@
     layer = FunctionalLayer
 
-    def test_import(self):
+    def test_import_create(self):
         self.processor = AccessCodeBatchProcessor()
         self.csv_file = os.path.join(self.workdir, 'sample_batch_data.csv')
@@ -126,5 +133,16 @@
         num, num_warns, fin_file, fail_file =  self.processor.doImport(
             self.csv_file, AC_HEADER_FIELDS)
-        self.assertEqual(num_warns,0)
+        self.assertEqual(num_warns,3)
+        fail_file = open(fail_file).read()
+        self.assertTrue(
+            'HOS-1-0007044547,HOS,anything,state: not allowed' in fail_file)
+        self.assertTrue(
+            'HOS-1-555,HOS,anything,workflow: not allowed' in fail_file)
+        self.assertTrue(
+            'HOS-1-666,HOS,anything,transition: not allowed' in fail_file)
+        self.assertEqual(
+            self.app['accesscodes']['HOS-1']['HOS-1-98769876'].state, 'used')
+        self.assertEqual(
+            self.app['accesscodes']['HOS-1']['HOS-1-76254765'].state, 'initialized')
         self.assertEqual(
             self.app['accesscodes']['CLR-1']['CLR-1-1625368961'].batch_serial, 33)
@@ -133,12 +151,52 @@
         self.assertEqual(
             self.app['accesscodes']['CLR-1']['CLR-1-1625368961'].state, 'used')
+        self.assertMatches(
+            self.app['accesscodes']['CLR-1']['CLR-1-1625368961'].history,
+            "<YYYY-MM-DD hh:mm:ss> UTC - initialized by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - state 'used' set by system")
+        self.assertMatches(
+            self.app['accesscodes']['HOS-1']['HOS-1-98769876'].history,
+            "<YYYY-MM-DD hh:mm:ss> UTC - initialized by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - used by system")
         logcontent = open(self.logfile).read()
         self.assertTrue(
             'INFO - system - AccessCode Processor - CLR-1-1625368961 - '
-            'AC updated: state=used, history=2012-07-11 14:51:56 UTC - '
-            'initialized by Manager||2012-07-11 14:55:42 UTC - '
-            'invalidated by Manager, batch_serial=33, '
-            'random_num=1625368961, cost=100.0, owner=K1000009'
+            'AC updated: state=used, batch_serial=33, random_num=1625368961, '
+            'cost=100.0, owner=K1000009'
             in logcontent)
         shutil.rmtree(os.path.dirname(fin_file))
         return
+
+    def test_import_update(self):
+        self.processor = AccessCodeBatchProcessor()
+        self.csv_file = os.path.join(self.workdir, 'sample_batch_data.csv')
+        open(self.csv_file, 'wb').write(BATCH_SAMPLE_DATA)
+        self.processor.doImport(self.csv_file, BATCH_HEADER_FIELDS)
+
+        self.processor = AccessCodeProcessor()
+        self.csv_file = os.path.join(self.workdir, 'sample_ac_data.csv')
+        open(self.csv_file, 'wb').write(AC_SAMPLE_DATA)
+        num, num_warns, fin_file, fail_file =  self.processor.doImport(
+            self.csv_file, AC_HEADER_FIELDS)
+
+        self.csv_file = os.path.join(self.workdir, 'sample_ac_data_update.csv')
+        open(self.csv_file, 'wb').write(AC_SAMPLE_DATA_UPDATE)
+        num, num_warns, fin_file, fail_file =  self.processor.doImport(
+            self.csv_file, AC_HEADER_FIELDS_UPDATE, 'update')
+        fin_file = open(fin_file).read()
+        self.assertEqual(num_warns,0)
+        self.assertEqual(
+            self.app['accesscodes']['HOS-1']['HOS-1-98769876'].state, 'disabled')
+        self.assertEqual(
+            self.app['accesscodes']['CLR-1']['CLR-1-1625368961'].state, 'disabled')
+        self.assertMatches(
+            self.app['accesscodes']['CLR-1']['CLR-1-1625368961'].history,
+            "<YYYY-MM-DD hh:mm:ss> UTC - initialized by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - state 'used' set by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - state 'disabled' set by system")
+        self.assertMatches(
+            self.app['accesscodes']['HOS-1']['HOS-1-98769876'].history,
+            "<YYYY-MM-DD hh:mm:ss> UTC - initialized by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - used by system|"
+            "|<YYYY-MM-DD hh:mm:ss> UTC - disabled by system")
+        return
