Index: /main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- /main/waeup.kofa/trunk/CHANGES.txt	(revision 13431)
+++ /main/waeup.kofa/trunk/CHANGES.txt	(revision 13432)
@@ -4,4 +4,6 @@
 1.3.4.dev0 (unreleased)
 =======================
+
+* Add BedProcessor.
 
 * Add portal maintenance mode.
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py	(revision 13431)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/batching.py	(revision 13432)
@@ -21,7 +21,10 @@
 import grok
 from zope.interface import Interface
+from zope.component import getUtility
+from zope.catalog.interfaces import ICatalog
 from waeup.kofa.interfaces import IBatchProcessor, IGNORE_MARKER
 from waeup.kofa.utils.batching import BatchProcessor
-from waeup.kofa.hostels.interfaces import IHostel
+from waeup.kofa.hostels.interfaces import IHostel, IBed
+from waeup.kofa.hostels.vocabularies import NOT_OCCUPIED
 from waeup.kofa.interfaces import MessageFactory as _
 
@@ -76,2 +79,105 @@
             % (self.name, filename, row[location_field], items_changed))
         return
+
+
+class BedProcessor(BatchProcessor):
+    """The Bed Procesor update beds. It does allocate students
+    to empty beds and switches the reservation status of beds.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'bedupdater'
+    grok.name(util_name)
+
+    name = _('Bed Processor (update only)')
+    iface = IBed
+
+    location_fields = ['hostel_id', 'bed_id']
+    factory_name = None
+
+    mode = None
+
+    @property
+    def available_fields(self):
+        return self.location_fields + ['reserved', 'owner']
+
+    def parentsExist(self, row, site):
+        if not 'hostels' in site.keys():
+            return False
+        return row['hostel_id'] in site['hostels']
+
+    def entryExists(self, row, site):
+        if not self.parentsExist(row, site):
+            return False
+        parent = self.getParent(row, site)
+        return row['bed_id'] in parent.keys()
+
+    def getParent(self, row, site):
+        return site['hostels'][row['hostel_id']]
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['bed_id'])
+
+    def checkUpdateRequirements(self, obj, row, site):
+        """Checks requirements the bed must fulfill
+        before being updated.
+        """
+        # Check if bed is occupied
+        if row.get('owner') and obj.owner != NOT_OCCUPIED:
+            return 'Bed is occupied.'
+
+    def checkConversion(self, row, mode='ignore'):
+        """Validates all values in row.
+        """
+        inv_errs = ''
+        conv_dict = {}
+        errs = []
+        reserved = row.get('reserved')
+        if reserved not in (None, IGNORE_MARKER, '', '0', '1'):
+            errs.append(('reserved','invalid value'))
+        owner = row.get('owner')
+        if owner not in (None, '', IGNORE_MARKER):
+            if owner == NOT_OCCUPIED:
+                errs.append(('owner','bed cannot be released by import'))
+                return errs, inv_errs, conv_dict
+            beds_cat = getUtility(ICatalog, name='beds_catalog')
+            results = list(beds_cat.searchResults(owner=(owner, owner)))
+            if len(results) > 0:
+                errs.append((
+                    'owner','student already resides in %s'
+                    % results[0].bed_id))
+                return errs, inv_errs, conv_dict
+            students_cat = getUtility(ICatalog, name='students_catalog')
+            results = list(students_cat.searchResults(student_id=(owner, owner)))
+            if len(results) != 1:
+                errs.append(('owner','student does not exist'))
+        return errs, inv_errs, conv_dict
+
+    def updateEntry(self, obj, row, site, filename):
+        """Update obj to the values given in row.
+        """
+        changed = []
+        owner = row.get('owner')
+        if owner not in (None, '', IGNORE_MARKER):
+            obj.bookBed(owner)
+            changed.append('owner=%s' % owner)
+        reserved = row.get('reserved')
+        sh, sex, bt = obj.bed_type.split('_')
+        if (reserved == '1' and bt != 'reserved') or \
+            (reserved == '0'and bt == 'reserved'):
+            message = obj.switchReservation()
+            changed.append(message)
+        # Log actions...
+        if changed:
+            items_changed = ', '.join(changed)
+        else:
+            items_changed = 'nothing'
+        location_field = self.location_fields[1]
+        grok.getSite()['hostels'].logger.info(
+            '%s - %s - %s - updated: %s'
+            % (self.name, filename, row[location_field], items_changed))
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_bed_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_bed_data.csv	(revision 13432)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/sample_bed_data.csv	(revision 13432)
@@ -0,0 +1,6 @@
+hostel_id,bed_id,owner,reserved
+block-a-upper-hostel,block-a-upper-hostel_A_101_A,,0
+block-a-upper-hostel,block-a-upper-hostel_A_101_B,,1
+block-a-upper-hostel,block-a-upper-hostel_A_101_C,,2
+block-a-upper-hostel,block-a-upper-hostel_A_101_D,K1000000,0
+block-a-upper-hostel,block-a-upper-hostel_A_101_E,K999,0
Index: /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py	(revision 13431)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/hostels/tests.py	(revision 13432)
@@ -38,5 +38,5 @@
 from waeup.kofa.hostels.container import HostelsContainer
 from waeup.kofa.hostels.hostel import Hostel, Bed
-from waeup.kofa.hostels.batching import HostelProcessor
+from waeup.kofa.hostels.batching import HostelProcessor, BedProcessor
 from waeup.kofa.hostels.export import BedExporter, HostelExporter
 from waeup.kofa.testing import (FunctionalLayer, FunctionalTestCase)
@@ -50,4 +50,11 @@
 
 HOSTEL_HEADER_FIELDS = HOSTEL_SAMPLE_DATA.split(
+    '\n')[0].split(',')
+
+BED_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_bed_data.csv'),
+    'rb').read()
+
+BED_HEADER_FIELDS = BED_SAMPLE_DATA.split(
     '\n')[0].split(',')
 
@@ -523,5 +530,5 @@
         shutil.rmtree(os.path.dirname(self.outfile))
 
-class HostelProcessorTest(HostelsFullSetup):
+class HostelBedProcessorTest(HostelsFullSetup):
 
     layer = FunctionalLayer
@@ -561,9 +568,7 @@
         return
 
-    def test_import_update(self):
+    def test_import_update_hostel_and_beds(self):
         self.processor = HostelProcessor()
         self.workdir = tempfile.mkdtemp()
-        self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
-        open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
         self.csv_file = os.path.join(self.workdir, 'sample_hostel_data.csv')
         open(self.csv_file, 'wb').write(HOSTEL_SAMPLE_DATA)
@@ -586,4 +591,41 @@
             "blocks_for_female=['A']"
             in logcontent)
+        # Update the beds of first hostel
+        self.app['hostels']['block-a-upper-hostel'].updateBeds()
+        # Import beds
+        self.processor = BedProcessor()
+        self.csv_file = os.path.join(self.workdir, 'sample_bed_data.csv')
+        open(self.csv_file, 'wb').write(BED_SAMPLE_DATA)
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, BED_HEADER_FIELDS, 'update')
+        self.assertEqual(num_warns,2)
+        fail_file_content = open(fail_file).read()
+        logcontent = open(self.logfile).read()
+        self.assertEqual(
+            'owner,bed_id,hostel_id,reserved,--ERRORS--\r\n'
+            '<IGNORE>,block-a-upper-hostel_A_101_C,block-a-upper-hostel,2,'
+            'reserved: invalid value\r\n'
+            'K999,block-a-upper-hostel_A_101_E,block-a-upper-hostel,0,'
+            'owner: student does not exist\r\n',
+            fail_file_content)
+        self.assertTrue(
+            'system - Bed Processor (update only) - sample_bed_data - '
+            'block-a-upper-hostel_A_101_A - updated: nothing'
+            in logcontent)
+        self.assertTrue(
+            'system - Bed Processor (update only) - sample_bed_data - '
+            'block-a-upper-hostel_A_101_B - updated: reserved'
+            in logcontent)
+        self.assertTrue(
+            'system - Bed Processor (update only) - sample_bed_data - '
+            'block-a-upper-hostel_A_101_D - updated: owner=K1000000'
+            in logcontent)
+        self.assertEqual(
+            self.app['hostels']['block-a-upper-hostel']
+                    ['block-a-upper-hostel_A_101_D'].owner, 'K1000000')
+        self.assertEqual(
+            self.app['hostels']['block-a-upper-hostel']
+                    ['block-a-upper-hostel_A_101_B'].bed_type,
+                    'regular_female_reserved')
         shutil.rmtree(os.path.dirname(fin_file))
         shutil.rmtree(self.workdir)
