2
\$\begingroup\$

The prompt is: that I should write a program for a customer management system for registering customers (in C programming language).

The program should allow registration management members to:

  • Add customer record (id, name, age, gender and city) to the file.
  • Print customer details (ID, name, age, gender and city)

(Note: If the user enters the gender other than “male or female”, the program should display the error message "Invalid input! (gender must be male or female)" and enforce the user to enter the correct gender.)

#include <stdio.h>
#include<stdlib.h>
#include<string.h>

int n_r=0;
struct customer                                                                         // using a struct will be better than an array since it
{                                                                                       // can store several data types unlike an array
    int id;
    char firstname[20];
    char lastname[20];

    int age;
    char gender[6];
    char city[20];
};

void displayMainMenu()                                                                  // print menu
{
    printf("\n -------------------------------------------------------------\n");
    printf("\n -------------Customer Management System ---------------------\n");
    printf("\n ------------------------* MAIN MENU *----------------------------\n");
    printf("\n |A/a:Enter A or a for Adding a Customer ");
    printf("\n |D/d:Enter D or d for Printing Customer Details ");
    printf("\n |E/e:Enter E or e for Exiting the Program |------------------\n");
}  

char inputAndCheck()
{
    char ch;
    while(1)
    {
        displayMainMenu();
        printf("\n Please enter your choice: ");
        scanf("%c",&ch);
        if(ch=='A'||ch=='a'||ch=='D'||ch=='d'||ch=='E'||ch=='e')
        break;
        else
        {
            printf("\n > Invalid selection! Please try again ");
        }
    }
    return ch;
}
    

int readCustomerAge()
{
    int age;
    while(1)
    {
        printf("\n Enter age: ");
        scanf("%d",&age);
        if(age>=15 && age<=90)

        break;
        else
        {
            printf("\n > Invalid selection! (Age must be between 15 and 90) ");
        }
    }

    return age;
}                                                                       

int readCustomerGender()
{
    char ch1[6];
    while(1)
    {
        printf("\n Enter gender: ");
        scanf("%s",ch1);
        if((strcmp(ch1,"male")==0)||(strcmp(ch1,"MALE")==0))
        {
            return 0;
        }
        else if((strcmp(ch1,"female")==0)||(strcmp(ch1,"FEMALE")==0))
        {
            return 1;
        }
        else{
            printf("\n > Invalid selection! (Gender must be male or female) ");
        }
    }
}

char* readCustomerCity()
{
    char c[20];
    printf("\n Enter city: ");
    scanf("%s",c);
    return c;
}

void displayCustomerDetails()
{
    FILE *fp;
    struct customer c;
    fp=fopen("customer.txt","r");
    if(fp==NULL)
    {
        printf("\n Error in opening a file.");
        exit(1);
    }
    while(fread(&c,sizeof(struct customer),1,fp))
    printf("\n Id:%d \n Name:%s %s \n Age=%d \n Gender=%s \n City=%s",c.id,c.firstname,c.lastname,c.age,c.gender,c.city);

    fclose(fp);
}

void addCustomerDetails()
{
    FILE *fp;
    struct customer c;
    char* gen;
    //char cit[30];
    if(n_r==0)
    {
        fp=fopen("customer.txt","w");
        if(fp==NULL)
        {
            printf("\n Error in opening a file.");
            exit(1);
        }
    }
    else
    {
        fp=fopen("customer.txt","a");
        if(fp==NULL)
        {
            printf("\n Error in opening a file.");
            exit(1);
        }
    }

    printf("\n Enter customer ID: ");
    scanf("%d",&c.id);
    printf("\n Enter customer first name: ");
    scanf("%s",&c.firstname);
    printf("\n Enter customer last name: ");
    scanf("%s",&c.lastname);
    c.age=readCustomerAge();
    if(readCustomerGender())
    {
        strcpy(c.gender,"Female");
    }
    else
    {
        strcpy(c.gender,"Male");
    }

    printf("\n Enter customer city: ");
    scanf("%s",&c.city);
    fwrite(&c,sizeof(struct customer),1,fp);
    fclose(fp); 
}

int main()
{
    int year;
    char ch;
    printf("\n -------------------------------------------------------------\n");
    printf("\n -------------Welcome to Customer Management System ---------------------\n");
    printf("\n > Please Enter Customer registration year (ex:2017):");
    scanf("%d",&year);
    printf("\n > How many customers do you want register in the year %d:",year);
    scanf("%d",&n_r);

    while(1)
    {
        ch=inputAndCheck();
        switch(ch)
        {
            case 'A':
            case 'a':
            addCustomerDetails();
            n_r++;
            break;

            case 'D':
            case 'd':
            displayCustomerDetails();
            break;
            
            case 'E':
            case 'e':
            printf("\n > Thank you for using Customer Management System!\n");
            printf("\n > Good Bye. \n");
            exit(0);
        }
    }

    return 0;
}

Please note, I still haven't finished writing the comments for the program but will continue writing them after I receive feedback.

\$\endgroup\$
9
  • \$\begingroup\$ Hi @A.R.A I guess the obvious problem is dealing with non-ascii characters - think about all those Arabic, Thai, Japanese, Chinese, Russian and others who wouldn't be able to enter their names in this system !! \$\endgroup\$
    – Mr R
    Apr 11, 2021 at 22:14
  • \$\begingroup\$ Hello @MrR . You do have a point. However in the prompt I was given the prof didn't include other languages. Therefore, it would be safe to assume that the language being used is English. The only problem I am facing now is that whenever I try running the code and choose option "D" after inputting data in option A, the program would print out the data of the user twice. Another issue I found, when I try running the code on Visual Studio, the code would terminate right after picking A or D. Is there any way I can fix this?? \$\endgroup\$
    – A.R.A
    Apr 11, 2021 at 22:29
  • \$\begingroup\$ Hi @A.R.A - hint wchar_t / #include <wchar.h> (for extra credit :-) .. AND strcasecmp [which ignores case OR do you really mean to prevent Female?] Also are you really meant to write a "binary" format to the file - or a structured text format (like CSV, JSON or something else??)? \$\endgroup\$
    – Mr R
    Apr 12, 2021 at 0:44
  • \$\begingroup\$ Hi @A.R.A. displayCustomerDetails() displays "all" customers details - so if you have >1 in the file then >1 will show .... ALSO fread can fail with an error - the code seems to just ignore that?? \$\endgroup\$
    – Mr R
    Apr 12, 2021 at 0:46
  • 1
    \$\begingroup\$ @A.R.A If the prompt didn't ask you to handle non-ASCII input, then I wouldn't worry about wchar_t. Focus instead on restructuring your code to make it more readable, and improving its error handling (especially for common input errors). \$\endgroup\$
    – cariehl
    Apr 12, 2021 at 5:05

1 Answer 1

1
\$\begingroup\$

Domain-specific types

Instead of

char gender[6];

if you're intent on constraining to two genders, you're better off making an enum. However, it's in general a poor choice to do this; instead just accept a free string and don't bother writing logic for M/F.

Pre-existing files

It seems the only useful role that n_r has currently is to decide the write mode of your database file. You can get rid of it entirely, and sort out the write mode based on I/O operations automatically.

Pre- or post-newline

In command-line interfaces, it's more typical to do

printf("Foo\n");

than

printf("\nFoo");

because - right from the very beginning of the program - we assume to get a blank line, so there's no point in making a second one. Another reason is that certain caching logic relies on suffix newlines, not prefix newlines, to know when to write to a stream.

inputAndCheck

You basically go over this logic in duplicate - once to check for valid selection character, another time to actually pay attention to the choice. Do this once instead, and also tolower() so that you only need to check one value.

Validation loops

scanf has... a lot of problems. It pollutes the stdin buffer, for one, making it difficult to recover from an error where e.g. someone enters text instead of an integer.

A common work-around is to replace it with an fgets/sscanf pair.

Separation of concerns

displayCustomerDetails actually does two things - reads from the file, and displays to stdout. You should separate this. Similarly for addCustomerDetails.

Unused inputs

You ask for - and totally disregard - the input for registration year; and the input for n_r is both confusing and not properly used. You can get rid of both.

Overruns

When scanning for a string into a fixed buffer, it's critical that you tell the input function what your buffer size is so that there's no overrun. C is particularly vulnerable to buffer overrun crashes and security holes.

Backus-Naur

 |E/e

is a little odd in terms of formatting. Users familiar with command-line interfaces understand a pipe to mean an "or" between two options; so seeing it here as a visual delimiter is somewhat jarring. EBNF would instead suggest E|e.

Suggested

This code offers one way to deal with (most of) the above:

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int n_r = 0;

typedef enum {
    MALE = 0,
    FEMALE = 1
} gender;

typedef struct tag_customer
{
    unsigned id;
    char firstname[20];
    char lastname[20];

    unsigned age;
    gender gender;
    char city[20];
} customer;

static void displayMainMenu()
{
    puts(
          "-------------------------------------------------------------"
        "\n-------------Customer Management System ---------------------"
        "\n------------------------* MAIN MENU *------------------------"
        "\n A|a: Add a Customer"
        "\n D|d: Print Customer Details"
        "\n E|e: Exit"
    );
}

static void scanLoop(const char *prompt, const char *fmt, void *dest)
{
    char buffer[80];
    int fields;
    do
    {
        printf(prompt);
        if (!fgets(buffer, sizeof(buffer), stdin))
        {
            perror("I/O error on stdin");
            exit(1);
        }

        fields = sscanf(buffer, fmt, dest);
    } while (fields != 1);
}

static char menuChoice()
{
    displayMainMenu();
    char choice;
    scanLoop("Please enter your choice: ", "%c", &choice);
    return tolower(choice);
}

static unsigned readCustomerAge()
{
    while (true)
    {
        unsigned age;
        scanLoop("Enter age: ", "%u", &age);
        if (age >= 15 && age <= 90)
            return age;
        puts("Invalid selection! (Age must be between 15 and 90)");
    }
}

static gender readCustomerGender()
{
    char buffer[10];
    while (true)
    {
        scanLoop("Enter gender (male|female): ", "%9s", buffer);

        for (char *p = buffer; *p; p++)
            *p = tolower(*p);

        if (!strcmp(buffer, "male"))
            return MALE;
        if (!strcmp(buffer, "female"))
            return FEMALE;

        puts("Invalid selection!");
    }
}

static void displayCustomer(const customer *c)
{
    printf(
        "Id: %u"
        "\nName: %s %s"
        "\nAge: %u"
        "\nGender: %s"
        "\nCity: %s"
        "\n",
        c->id, c->firstname, c->lastname, c->age,
        c->gender == MALE ? "male" : "female",
        c->city
    );
}

static void readCustomers(const char *filename)
{
    FILE *fp;
    customer c;
    fp = fopen(filename, "r");
    if (fp == NULL)
    {
        perror("Error opening file for customer display");
        exit(1);
    }

    while (fread(&c, sizeof(customer), 1, fp))
        displayCustomer(&c);

    fclose(fp);
}

static void scanCustomer(customer *c)
{
    scanLoop("Enter customer ID: ", "%u", &c->id);
    scanLoop("Enter customer first name: ", "%20s", c->firstname);
    scanLoop("Enter customer last name: ", "%20s", c->lastname);

    c->age = readCustomerAge();
    c->gender = readCustomerGender();

    scanLoop("Enter city: ", "%20s", c->city);
}

static void addCustomer(const char *filename)
{
    const char *mode;
    if (n_r == 0)
        mode = "w";
    else mode = "a";

    FILE *fp = fopen(filename, mode);
    if (fp == NULL)
    {
        perror("Error in opening customer file for writing");
        exit(1);
    }

    customer c;
    scanCustomer(&c);
    fwrite(&c, sizeof(customer), 1, fp);
    fclose(fp);
}

static unsigned readYear()
{
    unsigned year;
    while (true)
    {
        scanLoop("Please Enter Customer registration year (ex:2017): ", "%u", &year);
        if (year >= 2000 && year <= 2500)
            return year;
        puts("Year out of range");
    }
}

int main()
{
    printf(
          "------------------------------------------------------------------------"
        "\n-------------Welcome to Customer Management System ---------------------"
        "\n"
        "\n"
    );

    readYear();

    while (true)
    {
        switch (menuChoice())
        {
            case 'a':
                addCustomer("customer.txt");
                n_r++;
                break;

            case 'd':
                readCustomers("customer.txt");
                break;

            case 'e':
                puts(
                    "Thank you for using Customer Management System!"
                    "\nGoodbye."
                );
                return 0;

            default:
                puts("Invalid selection! Please try again.");
        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ You didn't explain why you added tag_customer and what the word tag means in this context. Why isn't a simple struct customer good enough? \$\endgroup\$ Apr 12, 2021 at 21:45
  • \$\begingroup\$ It is enough; though I find the typedef to be more convenient \$\endgroup\$
    – Reinderien
    Apr 12, 2021 at 21:46
  • \$\begingroup\$ In displayMainMenu, you wrote the \n at the beginning of the line, at least in the source code. Further above you said that it is usual to write the \n at the end of the line. \$\endgroup\$ Apr 12, 2021 at 21:46
  • \$\begingroup\$ I made an exception there because doing otherwise would make the source code jagged; but there's a way to make it uniform - so I'll make that edit. \$\endgroup\$
    – Reinderien
    Apr 12, 2021 at 21:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.