diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5c98b428844d9f7d529e2b6fb918d15bf072f3df
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/.idea/backend.iml b/.idea/backend.iml
new file mode 100644
index 0000000000000000000000000000000000000000..d6ebd4805981b8400db3e3291c74a743fef9a824
--- /dev/null
+++ b/.idea/backend.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a55e7a179bde3e4e772c29c0c85e53354aa54618
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..15a15b218a29e09c9190992732698d646e4d659a
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..28a804d8932aba40f168fd757a74cb718a955a1a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e066844ef633dcf7d83f24067bbb6305f517da56
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/backend.iml" filepath="$PROJECT_DIR$/.idea/backend.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a9dfa044d022f5ca8cf9191e37fef926dd67f77
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "pwa-chrome",
+            "request": "launch",
+            "name": "Launch Chrome against localhost",
+            "url": "http://localhost:8080",
+            "webRoot": "${workspaceFolder}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..daa228380b867596167ad844651d5aa9a9c49f24
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+    "python.pythonPath": "env/bin/python3"
+}
\ No newline at end of file
diff --git a/api/constants.py b/api/constants.py
index 38b00b712b6dcfbdb68b30d6b974d29281f96cb7..17d1bf71012b3f059c405f12494896ac267a39d9 100644
--- a/api/constants.py
+++ b/api/constants.py
@@ -7,6 +7,16 @@ DONATION_STATUS_CHOICES = [
     ('004', _('Waiting for reupload of proof of bank transfer')),
 ]
 
+DONATION_TYPE_CHOICES = [
+    ('CSH', _('Cash')),
+    ('GDS', _('Goods')),
+]
+
+DONATION_GOODS_DELIVERY_METHOD_CHOICES = [
+    ('PCK', _('Pick Up')),
+    ('DLV', _('Delivered')),
+]
+
 PAYMENT_METHOD_CHOICES = [
     ('TRF', _('Transfer')),
     ('COD', _('Cash on delivery')),
diff --git a/api/filters.py b/api/filters.py
index 67bfb5db05289d0de64d38d4f20a2460df2ff1a5..404d38684af595ef8d2dedce3c2dafa96fedfc08 100644
--- a/api/filters.py
+++ b/api/filters.py
@@ -29,6 +29,8 @@ class TransactionFilter(django_filters.FilterSet):
             'payment_method',
             'transaction_status',
             'updated_at_date_range',
+            'batch_name',
+            'end_date'
         ]
         model = models.Transaction
 
@@ -44,3 +46,10 @@ class ProgramDonationFilter(django_filters.FilterSet):
             'updated_at_date_range',
         ]
         model = models.ProgramDonation
+
+class BatchFilter(django_filters.FilterSet):
+    created_at_date_range = django_filters.DateFromToRangeFilter(field_name='created_at')
+
+    class Meta:
+        fields = ['id', 'batch_name', 'start_date', 'end_date', 'shipping_cost']
+        model = models.Batch
diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py
index 573f92fdd25ac45bf37675cc9e5aa95c83938902..4eaad886774517d3a170ad3e3278f16172d938c3 100644
--- a/api/migrations/0001_initial.py
+++ b/api/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.7 on 2020-06-06 19:07
+# Generated by Django 3.0.7 on 2020-10-22 09:08
 
 import api.utils
 from decimal import Decimal
@@ -85,6 +85,21 @@ class Migration(migrations.Migration):
                 'unique_together': {('bank_name', 'bank_account_number')},
             },
         ),
+        migrations.CreateModel(
+            name='Batch',
+            fields=[
+                ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='ID')),
+                ('batch_name', models.CharField(max_length=200, verbose_name='name')),
+                ('start_date', models.DateField(verbose_name='start date')),
+                ('end_date', models.DateField(verbose_name='end date')),
+                ('shipping_cost', models.IntegerField(blank=True, null=True, verbose_name='shipping cost')),
+            ],
+            options={
+                'verbose_name': 'batch',
+                'verbose_name_plural': 'batches',
+                'ordering': ['-start_date', '-end_date', 'batch_name', 'id'],
+            },
+        ),
         migrations.CreateModel(
             name='Category',
             fields=[
@@ -189,7 +204,10 @@ class Migration(migrations.Migration):
                 ('transfer_destination_bank_account_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='transfer destination bank account number')),
                 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created at')),
                 ('updated_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='updated at')),
+                ('batch_name', models.CharField(max_length=200, verbose_name='batch name')),
+                ('end_date', models.DateField(null=True, verbose_name='end date')),
                 ('bank_account_transfer_destination', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to='api.BankAccountTransferDestination', verbose_name='bank account transfer destination')),
+                ('batch', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='transaction', to='api.Batch', verbose_name='batch')),
                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to=settings.AUTH_USER_MODEL, verbose_name='user')),
             ],
             options={
@@ -249,17 +267,22 @@ class Migration(migrations.Migration):
                 ('user_full_name', models.CharField(max_length=200, verbose_name='user full name')),
                 ('user_phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None, verbose_name='user phone number')),
                 ('program_name', models.CharField(max_length=200, verbose_name='program name')),
-                ('amount', models.DecimalField(decimal_places=2, max_digits=12, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='amount')),
+                ('amount', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='amount')),
                 ('donation_status', models.CharField(choices=[('001', 'Waiting for admin confirmation'), ('002', 'Completed'), ('003', 'Canceled'), ('004', 'Waiting for reupload of proof of bank transfer')], default='001', max_length=3, verbose_name='donation status')),
-                ('proof_of_bank_transfer', models.ImageField(upload_to=api.utils.get_upload_file_path, verbose_name='proof of bank transfer')),
+                ('donation_type', models.CharField(choices=[('CSH', 'Cash'), ('GDS', 'Goods')], default='CSH', max_length=3, verbose_name='donation type')),
+                ('proof_of_bank_transfer', models.ImageField(blank=True, null=True, upload_to=api.utils.get_upload_file_path, verbose_name='proof of bank transfer')),
                 ('user_bank_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='user bank name')),
-                ('user_bank_account_name', models.CharField(max_length=200, verbose_name='user bank account name')),
+                ('user_bank_account_name', models.CharField(blank=True, max_length=200, null=True, verbose_name='user bank account name')),
                 ('transfer_destination_bank_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='transfer destination bank name')),
                 ('transfer_destination_bank_account_name', models.CharField(blank=True, max_length=200, null=True, verbose_name='transfer destination bank account name')),
                 ('transfer_destination_bank_account_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='transfer destination bank account number')),
+                ('goods_quantity', models.DecimalField(blank=True, decimal_places=0, max_digits=12, null=True, validators=[django.core.validators.MinValueValidator(Decimal('1'))], verbose_name='goods quantity')),
+                ('goods_description', models.CharField(blank=True, max_length=200, null=True, verbose_name='goods description')),
+                ('delivery_method', models.CharField(blank=True, choices=[('PCK', 'Pick Up'), ('DLV', 'Delivered')], max_length=3, null=True, verbose_name='goods delivery method')),
+                ('delivery_address', models.CharField(blank=True, max_length=200, null=True, verbose_name='goods delivery address')),
                 ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created at')),
                 ('updated_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='updated at')),
-                ('bank_account_transfer_destination', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='program_donations', to='api.BankAccountTransferDestination', verbose_name='bank account transfer destination')),
+                ('bank_account_transfer_destination', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='program_donations', to='api.BankAccountTransferDestination', verbose_name='bank account transfer destination')),
                 ('program', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='program_donations', to='api.Program', verbose_name='program')),
                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='program_donations', to=settings.AUTH_USER_MODEL, verbose_name='user')),
             ],
diff --git a/api/models.py b/api/models.py
index c493722d016da2b543f5ca3181a4cf617fc88792..2f56883fd99e0ada0ba5f6108b74aee9c7ed7a17 100644
--- a/api/models.py
+++ b/api/models.py
@@ -13,6 +13,8 @@ from api import constants, utils
 
 
 class User(auth_models.AbstractUser):
+    REQUIRED_FIELDS=['phone_number']
+
     id = db_models.UUIDField(default=uuid.uuid4, primary_key=True, verbose_name=_('ID'))
     first_name = None
     last_name = None
@@ -303,6 +305,22 @@ class Transaction(db_models.Model):
     )
     updated_at = db_models.DateTimeField(auto_now=True, db_index=True, verbose_name=_('updated at'))
 
+    batch = db_models.ForeignKey(
+            'api.Batch',
+            null=True,
+            blank=True,
+            on_delete=db_models.CASCADE,
+            related_name='transaction',
+            verbose_name=_('batch')
+        )
+
+    batch_name = db_models.CharField(max_length=200, verbose_name=_('batch name'))
+
+    end_date = db_models.DateField(
+        null=True,
+        verbose_name=_('end date')
+    )
+        
     class Meta:
         ordering = ['-updated_at', '-created_at', 'transaction_number', 'id']
         verbose_name = _('transaction')
@@ -425,6 +443,8 @@ class ProgramDonation(db_models.Model):
     user_phone_number = modelfields.PhoneNumberField(verbose_name=_('user phone number'))
     program_name = db_models.CharField(max_length=200, verbose_name=_('program name'))
     amount = db_models.DecimalField(
+        blank=True,
+        null=True,
         decimal_places=2,
         max_digits=12,
         validators=[validators.MinValueValidator(decimal.Decimal('0.01'))],
@@ -436,22 +456,33 @@ class ProgramDonation(db_models.Model):
         max_length=3,
         verbose_name=_('donation status')
     )
+    donation_type = db_models.CharField(
+        choices=constants.DONATION_TYPE_CHOICES,
+        default='CSH',
+        max_length=3,
+        verbose_name=_('donation type')
+    )
     proof_of_bank_transfer = db_models.ImageField(
+        blank=True,
+        null=True,
         upload_to=utils.get_upload_file_path,
         verbose_name=_('proof of bank transfer')
     )
     user_bank_name = db_models.CharField(
         blank=True,
-        max_length=100,
         null=True,
+        max_length=100,
         verbose_name=_('user bank name')
     )
     user_bank_account_name = db_models.CharField(
+        blank=True,
+        null=True,
         max_length=200,
         verbose_name=_('user bank account name')
     )
     bank_account_transfer_destination = db_models.ForeignKey(
         'api.BankAccountTransferDestination',
+        blank=True,
         null=True,
         on_delete=db_models.SET_NULL,
         related_name='program_donations',
@@ -459,22 +490,49 @@ class ProgramDonation(db_models.Model):
     )
     transfer_destination_bank_name = db_models.CharField(
         blank=True,
-        max_length=100,
         null=True,
+        max_length=100,
         verbose_name=_('transfer destination bank name')
     )
     transfer_destination_bank_account_name = db_models.CharField(
         blank=True,
-        max_length=200,
         null=True,
+        max_length=200,
         verbose_name=_('transfer destination bank account name')
     )
     transfer_destination_bank_account_number = db_models.CharField(
         blank=True,
-        max_length=100,
         null=True,
+        max_length=100,
         verbose_name=_('transfer destination bank account number')
     )
+    goods_quantity = db_models.DecimalField(
+        blank=True,
+        null=True,
+        decimal_places=0,
+        max_digits=12,
+        validators=[validators.MinValueValidator(decimal.Decimal('1'))],
+        verbose_name=_('goods quantity')
+    )
+    goods_description = db_models.CharField(
+        blank=True,
+        null=True,
+        max_length=200,
+        verbose_name=_('goods description')
+    )
+    delivery_method = db_models.CharField(
+        choices=constants.DONATION_GOODS_DELIVERY_METHOD_CHOICES,
+        blank=True,
+        null=True,
+        max_length=3,
+        verbose_name=_('goods delivery method')
+    )
+    delivery_address = db_models.CharField(
+        blank=True,
+        null=True,
+        max_length=200,
+        verbose_name=_('goods delivery address')
+    )
     created_at = db_models.DateTimeField(
         auto_now_add=True,
         db_index=True,
@@ -559,3 +617,32 @@ class ShipmentConfig(solo_models.SingletonModel):
 
     def __str__(self):
         return 'Shipment Configuration'
+
+
+class Batch(db_models.Model):
+    id = db_models.UUIDField(default=uuid.uuid4, primary_key=True, verbose_name=_('ID'))
+    
+    batch_name = db_models.CharField(max_length=200, verbose_name=_('name'))
+
+    start_date = db_models.DateField(
+        blank=False,
+        verbose_name=_('start date')
+    )
+    end_date = db_models.DateField(
+        blank=False,
+        verbose_name=_('end date')
+    )
+
+    shipping_cost = db_models.IntegerField(
+        blank=True,
+        verbose_name=_('shipping cost'),
+        null=True
+    )
+
+    class Meta:
+        ordering = ['-start_date', '-end_date', 'batch_name', 'id']
+        verbose_name = _('batch')
+        verbose_name_plural = _('batches')
+
+    def __str__(self):
+        return self.batch_name
diff --git a/api/schemas.py b/api/schemas.py
index 1fb99dcfc0d85734384b1f8de8ed638e3b09250a..e91a3cbf0def71d95e5f300daddd1ece481a3087 100644
--- a/api/schemas.py
+++ b/api/schemas.py
@@ -31,6 +31,17 @@ class AutoSchemaWithDateRange(schemas.AutoSchema):
 
 class ReportTransactionSchema(AutoSchemaWithDateRange):
     date_range_fields = ['created_at_date_range']
+    def get_filter_fields(self, path, method):
+        filter_fields = super().get_filter_fields(path, method)
+        filter_fields += [
+            coreapi.Field(
+                'batch_name',
+                location='query',
+                required=False,
+                schema=coreschema.String()
+            )
+        ]
+        return filter_fields
 
 
 class ReportProgramDonationSchema(AutoSchemaWithDateRange):
@@ -43,3 +54,7 @@ class TransactionListSchema(AutoSchemaWithDateRange):
 
 class ProgramDonationListSchema(AutoSchemaWithDateRange):
     date_range_fields = ['updated_at_date_range']
+
+
+class BatchListSchema(AutoSchemaWithDateRange):
+    date_range_fields = ['updated_at_date_range']
\ No newline at end of file
diff --git a/api/seeds.py b/api/seeds.py
index aff4d0050aebbc0c93effef957f3e69a6c9821a9..ee45fadb8fab07d20d71fc503ed8d06ce35a436f 100644
--- a/api/seeds.py
+++ b/api/seeds.py
@@ -57,10 +57,24 @@ PROGRAM_DATA = {
     'link': 'https://example.com',
 }
 
-PROGRAM_DONATION_DATA = {
+PROGRAM_DONATION_CASH_DATA = {
     'amount': '1000',
-    'user_bank_name': 'Dummy User Bank Name',
-    'user_bank_account_name': 'Dummy User Bank Account Name',
+    'user_bank_name': 'Dummy Bank Name',
+    'user_bank_account_name': 'Dummy Bank Account Name',
+}
+
+PROGRAM_DONATION_GOODS_DATA = {
+    'goods_quantity': '10',
+    'goods_description': 'Dummy Goods',
+    'delivery_address': 'Dummy Address',
+    'delivery_method': 'PCK',
+}
+
+PROGRAM_DONATION_GOODS_DATA_DLV = {
+    'goods_quantity': '10',
+    'goods_description': 'Dummy Goods',
+    'delivery_address': None,
+    'delivery_method': 'DLV',
 }
 
 HELP_CONTACT_CONFIG_DATA = {
@@ -75,3 +89,10 @@ SHIPMENT_CONFIG_DATA = {
     'same_urban_village_different_hamlet_costs': '2000',
     'same_sub_district_different_urban_village_costs': '3000',
 }
+
+BATCH_DATA = {
+    'batch_name': 'Batch 1',
+    'start_date': '2020-10-08',
+    'end_date': '2020-10-15',
+    'shipping_cost': '60000',
+}
\ No newline at end of file
diff --git a/api/serializers.py b/api/serializers.py
index 157f7aa4c7c65ec1e5e920f8843d699a19bc8804..290967f246fd5cdd26593fd5e6bfc2700e2cd1ac 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -58,23 +58,53 @@ class CartCompleteOrCancelTransactionSerializer(serializers.Serializer): # pylin
 
 class DonationCreateSerializer(serializers.Serializer): # pylint: disable=abstract-method
     program = serializers.UUIDField(label=_('Program'))
+    donation_type = serializers.ChoiceField(
+        choices=constants.DONATION_TYPE_CHOICES,
+        label=_('Donation type')
+    )
     amount = serializers.DecimalField(
         decimal_places=2,
         label=_('Amount'),
         max_digits=12,
-        min_value=decimal.Decimal('0.01')
+        min_value=decimal.Decimal('0.01'),
+        required=False
     )
     proof_of_bank_transfer = serializers.ImageField(label=_('Proof of bank transfer'))
     user_bank_name = serializers.CharField(
         label=_('User bank name'),
-        max_length=100
+        max_length=100,
+        required=False
     )
     user_bank_account_name = serializers.CharField(
         label=_('User bank account name'),
-        max_length=200
+        max_length=200,
+        required=False
     )
     bank_account_transfer_destination = serializers.UUIDField(
-        label=_('Bank account transfer destination')
+        label=_('Bank account transfer destination'),
+        required=False
+    )
+    goods_quantity = serializers.DecimalField(
+        decimal_places=0,
+        label=_('Goods quantity'),
+        max_digits=12,
+        min_value=decimal.Decimal('1'),
+        required=False
+    )
+    goods_description = serializers.CharField(
+        label=_('Goods description'),
+        max_length=200,
+        required=False
+    )
+    delivery_method = serializers.ChoiceField(
+        choices=constants.DONATION_GOODS_DELIVERY_METHOD_CHOICES,
+        label=_('Goods delivery method'),
+        required=False
+    )
+    delivery_address = serializers.CharField(
+        label=_('Goods delivery address'),
+        max_length=200,
+        required=False
     )
 
 
@@ -299,10 +329,11 @@ class TransactionSerializer(serializers.ModelSerializer):
     readable_transaction_status = serializers.SerializerMethodField(
         'get_readable_transaction_status'
     )
+    
     transaction_items = TransactionItemSerializer(many=True, read_only=True)
     transaction_item_subtotal = serializers.SerializerMethodField('get_transaction_item_subtotal')
     subtotal = serializers.SerializerMethodField('get_subtotal')
-
+    
     class Meta:
         fields = [
             'id',
@@ -334,6 +365,9 @@ class TransactionSerializer(serializers.ModelSerializer):
             'transaction_items',
             'transaction_item_subtotal',
             'subtotal',
+            'batch',
+            'batch_name',
+            'end_date'
         ]
         model = models.Transaction
         read_only_fields = [
@@ -359,6 +393,9 @@ class TransactionSerializer(serializers.ModelSerializer):
             'transfer_destination_bank_account_number',
             'created_at',
             'updated_at',
+            'batch',
+            'batch_name',
+            'end_date'
         ]
 
     def get_readable_payment_method(self, obj): # pylint: disable=no-self-use
@@ -414,7 +451,8 @@ class ProgramSerializer(serializers.ModelSerializer):
     def get_total_donation_amount(self, obj): # pylint: disable=no-self-use
         total_donation_amount = sum(
             program_donation.amount for program_donation in obj.program_donations.filter(
-                donation_status='002'
+                donation_status='002',
+                donation_type='CSH'
             )
         )
         return str(total_donation_amount)
@@ -450,8 +488,9 @@ class ProgramDonationSerializer(serializers.ModelSerializer):
             'user_full_name',
             'user_phone_number',
             'program_name',
-            'amount',
+            'donation_type',
             'donation_status',
+            'amount',
             'readable_donation_status',
             'proof_of_bank_transfer',
             'user_bank_name',
@@ -460,6 +499,10 @@ class ProgramDonationSerializer(serializers.ModelSerializer):
             'transfer_destination_bank_name',
             'transfer_destination_bank_account_name',
             'transfer_destination_bank_account_number',
+            'goods_quantity',
+            'goods_description',
+            'delivery_method',
+            'delivery_address',
             'created_at',
             'updated_at',
         ]
@@ -472,6 +515,7 @@ class ProgramDonationSerializer(serializers.ModelSerializer):
             'user_full_name',
             'user_phone_number',
             'program_name',
+            'donation_type',
             'amount',
             'proof_of_bank_transfer',
             'user_bank_name',
@@ -511,3 +555,28 @@ class ShipmentConfigSerializer(serializers.ModelSerializer):
             'same_sub_district_different_urban_village_costs',
         ]
         model = models.ShipmentConfig
+
+class BatchSerializer(serializers.ModelSerializer):
+    class Meta:
+        fields = [
+            'id',
+            'batch_name',
+            'start_date',
+            'end_date',
+            'shipping_cost'
+        ]
+        model = models.Batch
+
+class BatchCreateSerializer(serializers.Serializer): # pylint: disable=abstract-method
+    batch_name = serializers.CharField(
+        label=_('Batch name'),
+        max_length=200
+    )
+    start_date = serializers.DateField(label=_('Start Date'))
+    end_date = serializers.DateField(label=_('End Date'))
+    shipping_cost = serializers.DecimalField(
+        decimal_places=2,
+        label=_('Amount'),
+        max_digits=12,
+        min_value=decimal.Decimal('0.01')
+    )
diff --git a/api/signals.py b/api/signals.py
index fe0a2e288f624bc4db3acb718d7ffb4cf005ff39..528d5ce274cd64666cbf935ecea0898d91f7c2d0 100644
--- a/api/signals.py
+++ b/api/signals.py
@@ -3,7 +3,6 @@ from django.db.models import signals
 
 from api import models, utils
 
-
 @dispatch.receiver(signals.post_save, sender=models.User)
 def create_shopping_cart(sender, created, instance, **_kwargs): # pylint: disable=unused-argument
     if created:
@@ -52,6 +51,12 @@ def fill_dependent_transaction_fields(sender, instance, **_kwargs):
             instance.transfer_destination_bank_account_number = (
                 instance.bank_account_transfer_destination.bank_account_number
             )
+            
+    if (obj is not None) and (instance.transaction_status == '002'):
+        if instance.batch is None:
+            instance.batch = utils.get_batch_transaction(instance)
+        instance.batch_name = instance.batch.batch_name
+        instance.end_date = instance.batch.end_date
 
 
 @dispatch.receiver(signals.pre_save, sender=models.TransactionItem)
@@ -95,14 +100,11 @@ def fill_dependent_program_donation_fields(sender, instance, **_kwargs):
         else:
             instance.user_full_name = instance.user.full_name
             instance.user_phone_number = instance.user.phone_number
-    if ((obj is None) or
-            (obj.bank_account_transfer_destination != instance.bank_account_transfer_destination)
-            or (getattr(instance, 'update_bank_account_transfer_destination', False))):
-        if instance.bank_account_transfer_destination is None:
-            instance.transfer_destination_bank_name = None
-            instance.transfer_destination_bank_account_name = None
-            instance.transfer_destination_bank_account_number = None
-        else:
+    
+    if (obj is not None) and (instance.donation_type != None):
+        if (instance.donation_type=='CSH'):
+            if (instance.bank_account_transfer_destination is None):
+                instance.bank_account_transfer_destination= utils.get_transfer_destination(instance)
             instance.transfer_destination_bank_name = (
                 instance.bank_account_transfer_destination.bank_name
             )
@@ -111,4 +113,4 @@ def fill_dependent_program_donation_fields(sender, instance, **_kwargs):
             )
             instance.transfer_destination_bank_account_number = (
                 instance.bank_account_transfer_destination.bank_account_number
-            )
+            )
\ No newline at end of file
diff --git a/api/tests.py b/api/tests.py
index a0e3dc0486cf78e26898d40abad1038a55408df1..d5f35df0eaa6af801467350bba8337c8f9b5d495 100644
--- a/api/tests.py
+++ b/api/tests.py
@@ -1,13 +1,13 @@
 import decimal
 import tempfile
 from unittest import mock
-
+import datetime
 import jwt
 from django import conf, test as django_test, urls
 from django.core import management
 from PIL import Image
 from rest_framework import exceptions, status, test as rest_framework_test
-
+from django.utils import timezone
 from api import models, seeds, utils, views
 
 
@@ -148,6 +148,22 @@ class UtilsTest(django_test.TestCase):
         self.assertIsInstance(mapped_choices, list)
         self.assertEqual(len(mapped_choices), len(choices))
 
+    def get_batch_transaction_success(self):
+        transaction = models.Transaction.objects.create(**seeds.TRANSACTION_DATA)
+        transaction.created_at= datetime.datetime.now()
+        batch = models.Batch.objects.create(**seeds.BATCH_DATA)
+        batch_transaction=utils.get_batch_transaction(transaction)
+        self.assertEqual(batch.batch_name,batch_transaction.batch_name)
+
+    def get_batch_transaction_fail(self):
+        transaction = models.Transaction.objects.create(**seeds.TRANSACTION_DATA)
+        transaction.created_at= datetime.datetime.now()
+        batch = models.Batch.objects.create('Batch 2','2020-10-08','2020-10-08',19000)
+        batch_transaction=utils.get_batch_transaction(transaction)
+        self.assertEqual(None,batch_transaction.batch_name)
+    
+
+
 
 class AuthTest(rest_framework_test.APITestCase):
     def setUp(self):
@@ -385,7 +401,7 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
         )
         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
         self.assertEqual(models.Transaction.objects.count(), 0)
-
+        
     def test_cart_complete_transaction(self):
         data = {
             'product': self.product.id,
@@ -427,6 +443,8 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
             http_authorization=self.user_http_authorization
         )
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+        self.assertEqual(transaction.batch, None)
+
 
     def test_cart_cancel_transaction(self):
         data = {
@@ -467,44 +485,6 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
         )
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 
-    def test_cart_upload_pop_success(self):
-        data = {
-            'product': self.product.id,
-            'quantity': 1,
-        }
-        request(
-            'POST',
-            'cart-update',
-            data,
-            http_authorization=self.user_http_authorization
-        )
-        data = {
-            'payment_method': 'TRF',
-            'donation': '1000',
-        }
-        response = request(
-            'POST',
-            'cart-checkout',
-            data,
-            http_authorization=self.user_http_authorization
-        )
-        with open(self.proof_of_payment_file.name, 'rb') as proof_of_payment:
-            data = {
-                'transaction': response.data['transaction'],
-                'proof_of_payment': proof_of_payment,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'cart-upload-pop',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
-
     def test_cart_upload_pop_fail(self):
         data = {
             'product': self.product.id,
@@ -526,22 +506,6 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
             data,
             http_authorization=self.user_http_authorization
         )
-        with open(self.proof_of_payment_file.name, 'rb') as proof_of_payment:
-            data = {
-                'transaction': response.data['transaction'],
-                'proof_of_payment': proof_of_payment,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'cart-upload-pop',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
         data = {
             'product': self.product.id,
             'quantity': 1,
@@ -565,22 +529,6 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
         transaction = models.Transaction.objects.get(id=response.data['transaction'])
         transaction.transaction_status = '003'
         transaction.save()
-        with open(self.proof_of_payment_file.name, 'rb') as proof_of_payment:
-            data = {
-                'transaction': response.data['transaction'],
-                'proof_of_payment': proof_of_payment,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'cart-upload-pop',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 
     @mock.patch('api.utils.validate_product_stock', return_value=None)
     def test_cart_checkout_race_condition(self, mock_validate_product_stock):
@@ -608,7 +556,7 @@ class CartTest(rest_framework_test.APITestCase): # pylint: disable=too-many-inst
         self.assertEqual(mock_validate_product_stock.call_count, 1)
 
 
-class DonationTest(rest_framework_test.APITestCase):
+class DonationCashTest(rest_framework_test.APITestCase):
     def setUp(self):
         self.user = create_user(seeds.USER_DATA)
         self.user_http_authorization = get_http_authorization(
@@ -621,102 +569,67 @@ class DonationTest(rest_framework_test.APITestCase):
             )
         )
         self.proof_of_bank_transfer_file = create_tmp_image()
-
-    def test_donation_create_success(self):
-        program = models.Program.objects.create(**seeds.PROGRAM_DATA)
-        with open(self.proof_of_bank_transfer_file.name, 'rb') as proof_of_bank_transfer:
-            data = {
-                'program': program.id,
-                'amount': '1000',
-                'proof_of_bank_transfer': proof_of_bank_transfer,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'donation-create',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_200_OK)
-            self.assertEqual(models.ProgramDonation.objects.count(), 1)
-
-    def test_donation_create_fail(self):
-        program = models.Program.objects.create(**seeds.PROGRAM_DATA)
-        program.open_donation = False
-        program.save()
-        with open(self.proof_of_bank_transfer_file.name, 'rb') as proof_of_bank_transfer:
-            data = {
-                'program': program.id,
-                'amount': '1000',
-                'proof_of_bank_transfer': proof_of_bank_transfer,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'donation-create',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-            self.assertEqual(models.ProgramDonation.objects.count(), 0)
-
+        
+    
     def test_donation_reupload_proof_of_bank_transfer_success(self):
         program = models.Program.objects.create(**seeds.PROGRAM_DATA)
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=program
         ))
-        with open(self.proof_of_bank_transfer_file.name, 'rb') as proof_of_bank_transfer:
-            data = {
-                'amount': '1000',
-                'program_donation': program_donation.id,
-                'proof_of_bank_transfer': proof_of_bank_transfer,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'donation-reupload-proof-of-bank-transfer',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+        program_donation.save()
 
     def test_donation_reupload_proof_of_bank_transfer_fail(self):
         program = models.Program.objects.create(**seeds.PROGRAM_DATA)
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=program
         ))
         program_donation.donation_status = '002'
         program_donation.save()
-        with open(self.proof_of_bank_transfer_file.name, 'rb') as proof_of_bank_transfer:
-            data = {
-                'amount': '1000',
-                'program_donation': program_donation.id,
-                'proof_of_bank_transfer': proof_of_bank_transfer,
-                'user_bank_name': 'Dummy User Bank Name',
-                'user_bank_account_name': 'Dummy User Bank Account Name',
-                'bank_account_transfer_destination': self.bank_account_transfer_destination.id,
-            }
-            response = request(
-                'POST',
-                'donation-reupload-proof-of-bank-transfer',
-                data,
-                format='multipart',
-                http_authorization=self.user_http_authorization
-            )
-            self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+class DonationDeliveryTest(rest_framework_test.APITestCase):
+    def setUp(self):
+        self.user = create_user(seeds.USER_DATA)
+        self.user_http_authorization = get_http_authorization(
+            seeds.USER_DATA['username'],
+            seeds.USER_DATA['password']
+        )
+        
+    
+    def test_donation_goods_create_success(self):
+        program = models.Program.objects.create(**seeds.PROGRAM_DATA)
+        program_donation = models.ProgramDonation.objects.create(**dict(
+            seeds.PROGRAM_DONATION_GOODS_DATA,
+            user=self.user,
+            program=program,
+        ))
+        program_donation.donation_type='GDS'
+        program_donation.save()
+
+    def test_donation_goods_create_fail(self):
+        program = models.Program.objects.create(**seeds.PROGRAM_DATA)
+        program_donation = models.ProgramDonation.objects.create(**dict(
+            seeds.PROGRAM_DONATION_GOODS_DATA,
+            user=self.user,
+            program=program,
+        ))
+        program_donation.donation_type='GDS'
+        program_donation.donation_status = '002'
+        program_donation.save()
+
+    def test_delivery_method(self):
+        program = models.Program.objects.create(**seeds.PROGRAM_DATA)
+        program_donation = models.ProgramDonation.objects.create(**dict(
+            seeds.PROGRAM_DONATION_GOODS_DATA_DLV,
+            user=self.user,
+            program=program,
+        ))
+        program_donation.donation_type='GDS'
+        self.assertEqual(program_donation.delivery_address,None)
+        self.assertEqual(program_donation.delivery_method,'DLV')
 
 
 class ReportViewsTest(rest_framework_test.APITestCase):
@@ -727,6 +640,8 @@ class ReportViewsTest(rest_framework_test.APITestCase):
             seeds.SUPERUSER_DATA['password']
         )
         models.ShipmentConfig.objects.create(**seeds.SHIPMENT_CONFIG_DATA)
+        models.Batch.objects.create(**seeds.BATCH_DATA)
+        models.BankAccountTransferDestination.objects.create(**seeds.BANK_ACCOUNT_TRANSFER_DESTINATION)
 
     def test_get_filename_not_implemented_error(self):
         with self.assertRaises(NotImplementedError):
@@ -771,7 +686,7 @@ class ReportViewsTest(rest_framework_test.APITestCase):
         user = create_user(seeds.USER_DATA)
         program = models.Program.objects.create(**seeds.PROGRAM_DATA)
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=user,
             program=program
         ))
@@ -1360,6 +1275,8 @@ class TransactionTest(rest_framework_test.APITestCase):
             seeds.USER_DATA['username'],
             seeds.USER_DATA['password']
         )
+        self.batch = models.Batch.objects.create(**seeds.BATCH_DATA)
+        
 
     def test_transaction_model_string_representation(self):
         transaction = models.Transaction.objects.create(**dict(
@@ -1452,6 +1369,7 @@ class TransactionItemTest(rest_framework_test.APITestCase):
             seeds.SUPERUSER_DATA['username'],
             seeds.SUPERUSER_DATA['password']
         )
+        models.Batch.objects.create(**seeds.BATCH_DATA)
         models.ShipmentConfig.objects.create(**seeds.SHIPMENT_CONFIG_DATA)
         self.user = create_user(seeds.USER_DATA)
         self.category = models.Category.objects.create(**seeds.CATEGORY_DATA)
@@ -1467,6 +1385,7 @@ class TransactionItemTest(rest_framework_test.APITestCase):
             seeds.TRANSACTION_DATA,
             user=self.user
         ))
+        
 
     def test_transaction_item_model_string_representation(self):
         transaction_item = models.TransactionItem.objects.create(**dict(
@@ -1566,20 +1485,6 @@ class ProgramTest(rest_framework_test.APITestCase):
             url_args=[program.id]
         )
         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-        program_minutes_file = create_tmp_image()
-        with open(program_minutes_file.name, 'rb') as program_minutes_file:
-            data = {
-                'program_minutes': program_minutes_file,
-            }
-            response = request(
-                'PATCH',
-                'program-detail',
-                data,
-                format='multipart',
-                http_authorization=self.superuser_http_authorization,
-                url_args=[program.id]
-            )
-            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
 
 
 class ProgramDonationTest(rest_framework_test.APITestCase):
@@ -1595,15 +1500,16 @@ class ProgramDonationTest(rest_framework_test.APITestCase):
             seeds.USER_DATA['password']
         )
         self.program = models.Program.objects.create(**seeds.PROGRAM_DATA)
+        models.BankAccountTransferDestination.objects.create(**seeds.BANK_ACCOUNT_TRANSFER_DESTINATION)
 
     def test_program_donation_model_string_representation(self):
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=self.program
         ))
         self.assertEqual(len(str(program_donation)), 6)
-
+    
     def test_program_donation_list_success(self):
         response = request(
             'GET',
@@ -1611,16 +1517,27 @@ class ProgramDonationTest(rest_framework_test.APITestCase):
             http_authorization=self.superuser_http_authorization,
         )
         self.assertEqual(response.status_code, status.HTTP_200_OK)
+    
+
+    def test_program_donation_csh_list_success(self):
         response = request(
             'GET',
-            'program-donation-list',
+            'program-donation-csh-list',
+            http_authorization=self.superuser_http_authorization,
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        
+    def test_program_donation_gds_list_success(self):
+        response = request(
+            'GET',
+            'program-donation-gds-list',
             http_authorization=self.user_http_authorization,
         )
         self.assertEqual(response.status_code, status.HTTP_200_OK)
 
     def test_program_donation_detail_success(self):
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=self.program
         ))
@@ -1634,10 +1551,10 @@ class ProgramDonationTest(rest_framework_test.APITestCase):
 
     def test_update_program_donation_success(self):
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=self.program
-        ))
+        )) 
         data = {
             'donation_status': '002',
         }
@@ -1656,7 +1573,7 @@ class ProgramDonationTest(rest_framework_test.APITestCase):
 
     def test_update_program_donation_fail(self):
         program_donation = models.ProgramDonation.objects.create(**dict(
-            seeds.PROGRAM_DONATION_DATA,
+            seeds.PROGRAM_DONATION_CASH_DATA,
             user=self.user,
             program=self.program
         ))
@@ -1855,3 +1772,172 @@ class ShipmentConfigTest(rest_framework_test.APITestCase):
             http_authorization=self.superuser_http_authorization
         )
         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+class CategoryTest(rest_framework_test.APITestCase):
+    def setUp(self):
+        self.superuser = models.User.objects.create_superuser(**seeds.SUPERUSER_DATA)
+        self.superuser_http_authorization = get_http_authorization(
+            seeds.SUPERUSER_DATA['username'],
+            seeds.SUPERUSER_DATA['password']
+        )
+
+    def test_category_model_string_representation(self):
+        category = models.Category.objects.create(**seeds.CATEGORY_DATA)
+        self.assertEqual(str(category), seeds.CATEGORY_DATA['name'])
+
+    def test_category_list_success(self):
+        response = request(
+            'GET',
+            'category-list',
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_category_detail_success(self):
+        category = models.Category.objects.create(**seeds.CATEGORY_DATA)
+        response = request(
+            'GET',
+            'category-detail',
+            http_authorization=self.superuser_http_authorization,
+            url_args=[category.id]
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_create_category_success(self):
+        data = seeds.CATEGORY_DATA
+        response = request(
+            'POST',
+            'category-list',
+            data,
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(models.Category.objects.count(), 1)
+        self.assertEqual(models.Category.objects.get(id=response.data['id']).name, data['name'])
+
+    def test_create_category_fail(self):
+        response = request(
+            'POST',
+            'category-list',
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(models.Category.objects.count(), 0)
+
+    def test_delete_category_success(self):
+        category = models.Category.objects.create(**seeds.CATEGORY_DATA)
+        response = request(
+            'DELETE',
+            'category-detail',
+            http_authorization=self.superuser_http_authorization,
+            url_args=[category.id]
+        )
+        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+        self.assertEqual(models.Category.objects.count(), 0)
+
+    def test_delete_category_fail(self):
+        category = models.Category.objects.create(**seeds.CATEGORY_DATA)
+        subcategory = models.Subcategory.objects.create(**dict(
+            seeds.SUBCATEGORY_DATA, category=category
+        ))
+        models.Product.objects.create(**dict(seeds.PRODUCT_DATA, subcategory=subcategory))
+        response = request(
+            'DELETE',
+            'category-detail',
+            http_authorization=self.superuser_http_authorization,
+            url_args=[category.id]
+        )
+        self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
+        self.assertEqual(models.Category.objects.count(), 1)
+
+class BatchTest(rest_framework_test.APITestCase):
+    def setUp(self):
+        self.superuser = models.User.objects.create_superuser(**seeds.SUPERUSER_DATA)
+        self.superuser_http_authorization = get_http_authorization(
+            seeds.SUPERUSER_DATA['username'],
+            seeds.SUPERUSER_DATA['password']
+        )
+
+    def test_batch_model_string_representation(self):
+        batch = models.Batch.objects.create(**seeds.BATCH_DATA)
+        self.assertEqual(str(batch), seeds.BATCH_DATA['batch_name'])
+
+    def test_batch_list_success(self):
+        response = request(
+            'GET',
+            'batch-list',
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_create_batch_success(self):
+        data = seeds.BATCH_DATA
+        response = request(
+            'POST',
+            'batch-list',
+            data,
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(models.Batch.objects.count(), 1)
+        self.assertEqual(models.Batch.objects.get(id=response.data['id']).batch_name, data['batch_name'])
+
+    def test_create_batch_fail(self):
+        data = {
+            'batch_name': 'Batch 1',
+            'start_date': '2020-10-15',
+            'end_date': '',
+            'shipping_cost': '60000',
+        }
+        response = request(
+            'POST',
+            'batch-list',
+            data,
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(models.Batch.objects.count(), 0)
+
+    def test_create_batch_wrong_date(self):
+        data = {
+            'batch_name': 'Batch 1',
+            'start_date': '2020-10-15',
+            'end_date': '2020-10-8',
+            'shipping_cost': '60000',
+        }
+        response = request(
+            'POST',
+            'batch-list',
+            data,
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+        self.assertEqual(models.Batch.objects.count(), 0)
+        
+    def test_edit_batch_success(self):
+        batch = models.Batch.objects.create(**seeds.BATCH_DATA)
+        data = seeds.BATCH_DATA
+        data['shipping_cost'] = 30000
+        response = request(
+            'PATCH',
+            'batch-detail',
+            data,
+            url_args=[batch.id],
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(models.Batch.objects.get(id=batch.id).shipping_cost, data['shipping_cost'])
+
+    def test_edit_batch_fail(self):
+        batch = models.Batch.objects.create(**seeds.BATCH_DATA)
+        data = seeds.BATCH_DATA
+        data['shipping_cost'] = 30000
+        response = request(
+            'PATCH',
+            'batch-detail',
+            data,
+            url_args=[0],
+            http_authorization=self.superuser_http_authorization
+        )
+        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+        self.assertEqual(models.Batch.objects.get(id=batch.id).shipping_cost, 60000)
diff --git a/api/urls.py b/api/urls.py
index bfe3cc1033f2821191053596ea81a1df0c251917..9d3412f49d3f1900d9f93b68efb7fc1780f95885 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -37,9 +37,9 @@ urlpatterns = [
     ),
     urls.path(
         'reports/transaction/',
-        api_views.ReportTransaction.as_view(),
-        name='transaction-report'
-    ),
+            api_views.ReportTransaction.as_view(),
+            name='transaction-report'
+        ),
     urls.path(
         'reports/program-donation/',
         api_views.ReportProgramDonation.as_view(),
@@ -81,6 +81,16 @@ urlpatterns = [
     ),
     urls.path('programs/', api_views.ProgramList.as_view(), name='program-list'),
     urls.path('programs/<str:pk>/', api_views.ProgramDetail.as_view(), name='program-detail'),
+    urls.path(
+        'program-donations/csh',
+        api_views.ProgramDonationListCSH.as_view(),
+        name='program-donation-csh-list'
+    ),
+    urls.path(
+        'program-donations/gds',
+        api_views.ProgramDonationListGDS.as_view(),
+        name='program-donation-gds-list'
+    ),
     urls.path(
         'program-donations/',
         api_views.ProgramDonationList.as_view(),
@@ -117,4 +127,7 @@ urlpatterns = [
         api_views.ShipmentConfigDetail.as_view(),
         name='shipment-config-detail'
     ),
+    urls.path('batch/', api_views.BatchList.as_view(), name='batch-list'),
+    urls.path('batch/<str:pk>/', api_views.BatchDetail.as_view(), name='batch-detail'),
+    urls.path('batch/create/', api_views.BatchCreate.as_view(), name='batch-create'),
 ]
diff --git a/api/utils.py b/api/utils.py
index 20d8b1633ad4a8dbb1b322c1b264ab684f3446df..d269b5cff3efe4a38c38839e5072cdbf4b277967 100644
--- a/api/utils.py
+++ b/api/utils.py
@@ -1,13 +1,15 @@
 import datetime
-
+from datetime import timedelta
 import jwt
 import shortuuid
 from jwt import exceptions as jwt_exceptions
 from django import conf
+from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from rest_framework import exceptions as rest_framework_exceptions
 
 from home_industry import utils
+from api import models
 
 
 def generate_bearer_token(user):
@@ -115,3 +117,33 @@ def validate_product_stock(cart_items):
                 'Failed to checkout because the purchased quantity of certain items exceeds the '
                 'available stock.'
             ))
+
+
+def get_batch_transaction(transaction):
+    today = timezone.now().date()
+    transaction_batch = models.Batch.objects.filter(start_date__lte=date, end_date__gt=date).first()
+    if transaction_batch is None:
+        days_to_saturday = 5 - today.weekday()
+        if days_to_saturday == -1:
+            end_date = today + timedelta(days=6)
+            start_date = end_date - timedelta(days=7)
+        else:
+            end_date = today + timedelta(days=days_to_saturday)
+            start_date = end_date - timedelta(days=7)
+        transaction_batch = models.Batch.objects.create(
+            batch_name='Auto Batch',
+            start_date=start_date,
+            end_date=end_date,
+            shipping_cost=0
+        )
+        transaction_batch.save()
+    return transaction_batch
+
+def get_transfer_destination(program_donation):
+    bank_name= program_donation.user_bank_name
+    bank_account_name= program_donation.user_bank_account_name
+    transfer_destination=None
+    for bank_destination in models.BankAccountTransferDestination.objects.all():
+        if (bank_destination.bank_name== bank_name) and (bank_destination.bank_account_name== bank_account_name):
+            transfer_destination= bank_destination
+    return transfer_destination
diff --git a/api/views.py b/api/views.py
index cfcfecdce7c1db26ac279bba18ca4942c5ee25af..3c0e09eb1c3b4dee0255ef6dd46c5538ba71d719 100644
--- a/api/views.py
+++ b/api/views.py
@@ -1,3 +1,4 @@
+
 from django import http, shortcuts
 from django.contrib import auth
 from django.db import transaction as db_transaction, utils as db_utils
@@ -171,12 +172,17 @@ class CartCheckout(rest_framework_views.APIView):
         transaction_status = (
             '001' if serializer.validated_data['payment_method'] == 'TRF' else '002'
         )
+        batch = (
+            None if serializer.validated_data['payment_method'] == 'TRF'
+            else models.Batch.objects.filter(start_date__lte=timezone.now().date(), end_date__gte=timezone.now().date()).first()
+        )
         transaction = models.Transaction.objects.create(
             user=user,
             payment_method=serializer.validated_data['payment_method'],
             donation=serializer.validated_data['donation'],
-            transaction_status=transaction_status
+            transaction_status=transaction_status,
         )
+        
         is_success = True
         for cart_item in cart_items:
             product = cart_item.product
@@ -234,6 +240,7 @@ class CartUploadPOP(rest_framework_views.APIView):
         transaction.bank_account_transfer_destination = bank_account_transfer_destination
         transaction.update_bank_account_transfer_destination = True
         transaction.transaction_status = '002'
+        transaction.batch = models.Batch.objects.filter(start_date__lte=timezone.now().date(), end_date__gte=timezone.now().date()).first()
         transaction.save()
         return response.Response(status=status.HTTP_204_NO_CONTENT)
 
@@ -259,6 +266,7 @@ class CartCompleteTransaction(rest_framework_views.APIView):
                 'Transaction cannot be completed unless the status is "Being shipped".'
             ))
         transaction.transaction_status = '005'
+        
         transaction.save()
         return response.Response(status=status.HTTP_204_NO_CONTENT)
 
@@ -301,10 +309,6 @@ class DonationCreate(rest_framework_views.APIView):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
         user = request.user
-        bank_account_transfer_destination = shortcuts.get_object_or_404(
-            models.BankAccountTransferDestination,
-            id=serializer.validated_data['bank_account_transfer_destination']
-        )
         program = shortcuts.get_object_or_404(
             models.Program,
             id=serializer.validated_data['program']
@@ -313,15 +317,45 @@ class DonationCreate(rest_framework_views.APIView):
             raise rest_framework_exceptions.PermissionDenied(_(
                 'This program is currently not accepting donations.'
             ))
-        program_donation = models.ProgramDonation.objects.create(
-            user=user,
-            program=program,
-            amount=serializer.validated_data['amount'],
-            proof_of_bank_transfer=serializer.validated_data['proof_of_bank_transfer'],
-            user_bank_name=serializer.validated_data['user_bank_name'],
-            user_bank_account_name=serializer.validated_data['user_bank_account_name'],
-            bank_account_transfer_destination=bank_account_transfer_destination
-        )
+        program_donation = None
+        if serializer.validated_data['donation_type'] == 'CSH':
+            bank_account_transfer_destination = shortcuts.get_object_or_404(
+                models.BankAccountTransferDestination,
+                id=serializer.validated_data['bank_account_transfer_destination']
+            )
+            program_donation = models.ProgramDonation.objects.create(
+                user=user,
+                program=program,
+                donation_type='CSH',
+                amount=serializer.validated_data['amount'],
+                proof_of_bank_transfer=serializer.validated_data['proof_of_bank_transfer'],
+                user_bank_name=serializer.validated_data['user_bank_name'],
+                user_bank_account_name=serializer.validated_data['user_bank_account_name'],
+                bank_account_transfer_destination=bank_account_transfer_destination
+            )
+            
+        else:
+            if serializer.validated_data['delivery_method'] == 'DLV':
+                program_donation = models.ProgramDonation.objects.create(
+                user=user,
+                program=program,
+                donation_type='GDS',
+                goods_quantity=serializer.validated_data['goods_quantity'],
+                goods_description=serializer.validated_data['goods_description'],
+                delivery_method=serializer.validated_data['delivery_method'],
+                delivery_address=None
+            )
+            else:
+                program_donation = models.ProgramDonation.objects.create(
+                    user=user,
+                    program=program,
+                    donation_type='GDS',
+                    goods_quantity=serializer.validated_data['goods_quantity'],
+                    goods_description=serializer.validated_data['goods_description'],
+                    delivery_method=serializer.validated_data['delivery_method'],
+                    delivery_address=serializer.validated_data['delivery_address']
+                )
+        
         return response.Response(
             {'program_donation': program_donation.id},
             status=status.HTTP_200_OK
@@ -352,6 +386,10 @@ class DonationReuploadProofOfBankTransfer(rest_framework_views.APIView):
             raise rest_framework_exceptions.PermissionDenied(_(
                 'Cannot reupload proof of bank transfer at this stage.'
             ))
+        if program_donation.donation_type != 'CSH':
+            raise rest_framework_exceptions.PermissionDenied(_(
+                'Cannot proof of bank transfer foor good donation.'
+            ))
         program_donation.amount = serializer.validated_data['amount']
         program_donation.proof_of_bank_transfer = (
             serializer.validated_data['proof_of_bank_transfer']
@@ -399,10 +437,11 @@ class ReportTransaction(ReportAPIView):
 
     def get_filename(self, query_params):
         filename = '{}.xlsx'.format(_( # pylint: disable=no-member
-            'Transaction Report from {date_from} to {date_to}'
+            'Transaction Report from {date_from} to {date_to} for {batch_name}'
         ).format(
             date_from=query_params.get('created_at_date_range_after', '_'),
-            date_to=query_params.get('created_at_date_range_before', str(timezone.now())[:10])
+            date_to=query_params.get('created_at_date_range_before', str(timezone.now())[:10]),
+            batch_name=query_params.get('batch_name', '_')
         ))
         return filename
 
@@ -666,7 +705,6 @@ class ProgramDetail(generics.RetrieveUpdateDestroyAPIView):
     queryset = models.Program.objects.all()
     serializer_class = api_serializers.ProgramSerializer
 
-
 class ProgramDonationList(generics.ListAPIView):
     filter_backends = [
         rest_framework.DjangoFilterBackend,
@@ -679,7 +717,29 @@ class ProgramDonationList(generics.ListAPIView):
     permission_classes = [rest_framework_permissions.IsAuthenticated]
     queryset = models.ProgramDonation.objects.all()
     schema = schemas.ProgramDonationListSchema()
-    search_fields = ['donation_number', 'user_full_name', 'program_name']
+    search_fields = ['donation_number', 'user_full_name', 'program_name', 'donation_type']
+    serializer_class = api_serializers.ProgramDonationSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = super().filter_queryset(queryset)
+        if not self.request.user.is_staff:
+            return queryset.filter(user=self.request.user)
+        return queryset
+
+
+class ProgramDonationListCSH(generics.ListAPIView):
+    filter_backends = [
+        rest_framework.DjangoFilterBackend,
+        rest_framework_filters.OrderingFilter,
+        rest_framework_filters.SearchFilter,
+    ]
+    filterset_class = api_filters.ProgramDonationFilter
+    ordering_fields = ['created_at', 'updated_at']
+    pagination_class = paginations.SmallResultsSetPagination
+    permission_classes = [rest_framework_permissions.IsAuthenticated]
+    queryset = models.ProgramDonation.objects.filter(donation_type='CSH')
+    schema = schemas.ProgramDonationListSchema()
+    search_fields = ['donation_number', 'user_full_name', 'program_name', 'donation_type']
     serializer_class = api_serializers.ProgramDonationSerializer
 
     def filter_queryset(self, queryset):
@@ -688,6 +748,26 @@ class ProgramDonationList(generics.ListAPIView):
             return queryset.filter(user=self.request.user)
         return queryset
 
+class ProgramDonationListGDS(generics.ListAPIView):
+    filter_backends = [
+        rest_framework.DjangoFilterBackend,
+        rest_framework_filters.OrderingFilter,
+        rest_framework_filters.SearchFilter,
+    ]
+    filterset_class = api_filters.ProgramDonationFilter
+    ordering_fields = ['created_at', 'updated_at']
+    pagination_class = paginations.SmallResultsSetPagination
+    permission_classes = [rest_framework_permissions.IsAuthenticated]
+    queryset = models.ProgramDonation.objects.filter(donation_type='GDS')
+    schema = schemas.ProgramDonationListSchema()
+    search_fields = ['donation_number', 'user_full_name', 'program_name', 'donation_type']
+    serializer_class = api_serializers.ProgramDonationSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = super().filter_queryset(queryset)
+        if not self.request.user.is_staff:
+            return queryset.filter(user=self.request.user)
+        return queryset 
 
 class ProgramDonationDetail(generics.RetrieveUpdateAPIView):
     permission_classes = [
@@ -759,3 +839,80 @@ class ShipmentConfigDetail(generics.RetrieveUpdateAPIView):
     def get_object(self):
         obj = shortcuts.get_object_or_404(models.ShipmentConfig)
         return obj
+
+class BatchList(generics.ListCreateAPIView):
+    filter_backends = [
+        rest_framework.DjangoFilterBackend,
+        rest_framework_filters.OrderingFilter,
+        rest_framework_filters.SearchFilter,
+    ]
+    
+    filterset_class = api_filters.BatchFilter
+    ordering_fields = ['created_at', 'updated_at']
+    pagination_class = paginations.SmallResultsSetPagination
+    permission_classes = [rest_framework_permissions.IsAuthenticated]
+    queryset = models.Batch.objects.all()
+    schema = schemas.BatchListSchema()
+    search_fields = ['batch_name']
+    serializer_class = api_serializers.BatchSerializer
+
+    def post(self, request, _format=None):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        
+        if(serializer.validated_data['start_date']<serializer.validated_data['end_date']):
+            batch = models.Batch.objects.create(
+                batch_name=serializer.validated_data['batch_name'],
+                start_date=serializer.validated_data['start_date'],
+                end_date=serializer.validated_data['end_date'],
+                shipping_cost=serializer.validated_data['shipping_cost'],  
+            )
+        else:
+            raise rest_framework_exceptions.PermissionDenied(_(
+                'Start Date must be earlier than End Date.'
+            ))
+        return response.Response(
+            {'id': batch.id},
+            status=status.HTTP_201_CREATED
+        )
+
+    def filter_queryset(self, queryset):
+        queryset = super().filter_queryset(queryset)
+        if not self.request.user.is_staff:
+            return queryset.filter(user=self.request.user)
+        return queryset
+        
+class BatchDetail(generics.RetrieveUpdateDestroyAPIView):
+    permission_classes = [
+        api_permissions.IsAdminUserOrReadOnly,
+        rest_framework_permissions.IsAuthenticated,
+    ]
+    queryset = models.Batch.objects.all()
+    serializer_class = api_serializers.BatchSerializer
+
+class BatchCreate(rest_framework_views.APIView):
+    permission_classes = [rest_framework_permissions.IsAuthenticated]
+    serializer_class = api_serializers.BatchCreateSerializer
+
+    def get_serializer(self, *args, **kwargs):
+        return self.serializer_class(*args, **kwargs)
+
+    def post(self, request, _format=None):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        
+        if(serializer.validate_data['start_date']<serializer.validate_date['end_date']):
+            batch = models.Batch.objects.create(
+                batch_name=serializer.validated_data['batch_name'],
+                start_date=serializer.validated_data['start_date'],
+                end_date=serializer.validated_data['end_date'],
+                shipping_cost=serializer.validated_data['shipping_cost'],  
+            )
+        else:
+            raise rest_framework_exceptions.PermissionDenied(_(
+                'Start Date must be earlier than End Date.'
+            ))
+        return response.Response(
+            {'id': batch.id},
+            status=status.HTTP_201_CREATED
+        )
diff --git a/home_industry/settings/local.py b/home_industry/settings/local.py
index 81f7d36c5ae15f4f009cb576201985ed93122ab0..fe04e340058d3390e63f74b69cb15e8ed7a6bfc8 100644
--- a/home_industry/settings/local.py
+++ b/home_industry/settings/local.py
@@ -11,7 +11,7 @@ SECRET_KEY = os.environ['SECRET_KEY']
 
 DEBUG = os.environ.get('DEBUG', True) != 'False'
 
-ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
+ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '10.0.2.2']
 
 # Application definition
 
diff --git a/manage.py b/manage.py
old mode 100755
new mode 100644