diff --git a/gams/general_administration_management_system/doctype/communication_log/__init__.py b/gams/general_administration_management_system/doctype/communication_log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gams/general_administration_management_system/doctype/communication_log/communication_log.js b/gams/general_administration_management_system/doctype/communication_log/communication_log.js new file mode 100644 index 0000000..22b9678 --- /dev/null +++ b/gams/general_administration_management_system/doctype/communication_log/communication_log.js @@ -0,0 +1,23 @@ +// Copyright (c) 2026, efeone and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Communication Log', { + onload: function(frm) { + set_logged_in_employee(frm); + } +}); + +/* + * Populate the "Logged By" field with the Employee linked to the current session user. +*/ +function set_logged_in_employee(frm) { + if (!frm.doc.logged_by) { + frappe.db.get_value('Employee', { + user_id: frappe.session.user + }, 'name').then(r => { + if (r.message && r.message.name) { + frm.set_value('logged_by', r.message.name); + } + }); + } +} diff --git a/gams/general_administration_management_system/doctype/communication_log/communication_log.json b/gams/general_administration_management_system/doctype/communication_log/communication_log.json new file mode 100644 index 0000000..ff7eefd --- /dev/null +++ b/gams/general_administration_management_system/doctype/communication_log/communication_log.json @@ -0,0 +1,121 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "CML-.YYYY.-.####", + "creation": "2026-04-08 13:44:29.225107", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "communication_type", + "subject", + "from_party", + "to_party", + "column_break_stha", + "communication_date", + "logged_by", + "reference_doctype", + "reference_name", + "attachments", + "section_break_sqts", + "remarks" + ], + "fields": [ + { + "fieldname": "communication_type", + "fieldtype": "Select", + "label": "Communication Type", + "options": "Letter\nNotice\nCircular\nEmail\nMemo" + }, + { + "fieldname": "subject", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Subject", + "reqd": 1 + }, + { + "fieldname": "from_party", + "fieldtype": "Data", + "label": "From Party", + "reqd": 1 + }, + { + "fieldname": "to_party", + "fieldtype": "Data", + "label": "To Party", + "reqd": 1 + }, + { + "fieldname": "column_break_stha", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "communication_date", + "fieldtype": "Date", + "label": "Communication Date", + "reqd": 1 + }, + { + "fieldname": "logged_by", + "fieldtype": "Link", + "label": "Logged By", + "options": "Employee", + "read_only": 1 + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference Doctype", + "options": "DocType" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_doctype" + }, + { + "fieldname": "attachments", + "fieldtype": "Attach", + "label": "Attachments" + }, + { + "fieldname": "section_break_sqts", + "fieldtype": "Section Break" + }, + { + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-04-08 13:44:29.225107", + "modified_by": "admin@example.com", + "module": "General Administration Management System", + "name": "Communication Log", + "naming_rule": "Expression", + "owner": "admin@example.com", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/gams/general_administration_management_system/doctype/communication_log/communication_log.py b/gams/general_administration_management_system/doctype/communication_log/communication_log.py new file mode 100644 index 0000000..86de6d9 --- /dev/null +++ b/gams/general_administration_management_system/doctype/communication_log/communication_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, efeone and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class CommunicationLog(Document): + pass diff --git a/gams/general_administration_management_system/doctype/communication_log/test_communication_log.py b/gams/general_administration_management_system/doctype/communication_log/test_communication_log.py new file mode 100644 index 0000000..d12614a --- /dev/null +++ b/gams/general_administration_management_system/doctype/communication_log/test_communication_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, efeone and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestCommunicationLog(FrappeTestCase): + pass diff --git a/gams/general_administration_management_system/doctype/meeting_room_booking/__init__.py b/gams/general_administration_management_system/doctype/meeting_room_booking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.js b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.js new file mode 100644 index 0000000..5d34db8 --- /dev/null +++ b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.js @@ -0,0 +1,50 @@ +// Copyright (c) 2026, efeone and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Meeting Room Booking', { + onload: function(frm) { + set_meeting_room_filter(frm); + set_logged_in_employee(frm); + } +}); + +/** + * Ensures only rooms with status = "Active" + * are available for selection + */ +function set_meeting_room_filter(frm) { + frm.set_query('meeting_room', function() { + return { + filters: { + status: 'Active' + } + }; + }); +} + +/* + * Populate the "booked_by" field with the Employee linked to the current session user. +*/ +function set_logged_in_employee(frm) { + if (!frm.doc.booked_by) { + + // Get current logged-in user + const user = frappe.session.user; + + if (!user) { + console.log("Session user not found"); + return; + } + + // Fetch Employee linked to user + frappe.db.get_value('Employee', { + user_id: user + }, 'name').then(r => { + if (r.message && r.message.name) { + frm.set_value('booked_by', r.message.name); + } else { + frappe.msgprint("No Employee linked to this user"); + } + }); + } +} diff --git a/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.json b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.json new file mode 100644 index 0000000..cdcd21e --- /dev/null +++ b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.json @@ -0,0 +1,123 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "RBK-.YYYY.-.####", + "creation": "2026-04-08 13:43:45.894498", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "meeting_room", + "booked_by", + "employee_name", + "column_break_xgfu", + "booking_date", + "from_time", + "to_time", + "section_break_tmdv", + "description", + "status", + "section_break_fwch", + "attendees" + ], + "fields": [ + { + "fieldname": "meeting_room", + "fieldtype": "Link", + "label": "Meeting Room", + "options": "Room", + "reqd": 1 + }, + { + "fieldname": "booked_by", + "fieldtype": "Link", + "label": "Booked By", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "booked_by.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "column_break_xgfu", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "booking_date", + "fieldtype": "Date", + "label": "Booking Date" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + }, + { + "fieldname": "section_break_tmdv", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Pending\nApproved\nRejected\nCancelled" + }, + { + "fieldname": "section_break_fwch", + "fieldtype": "Section Break" + }, + { + "fieldname": "attendees", + "fieldtype": "Table", + "label": "Attendees", + "options": "Resource Booking Attendee" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-04-08 13:43:45.894498", + "modified_by": "admin@example.com", + "module": "General Administration Management System", + "name": "Meeting Room Booking", + "naming_rule": "Expression", + "owner": "admin@example.com", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.py b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.py new file mode 100644 index 0000000..c0cb1f2 --- /dev/null +++ b/gams/general_administration_management_system/doctype/meeting_room_booking/meeting_room_booking.py @@ -0,0 +1,86 @@ +# Copyright (c) 2026, efeone and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.utils import getdate, nowdate, get_time + +class MeetingRoomBooking(Document): + + def validate(self): + self.validate_time() + self.validate_date() + self.validate_room_availability() + self.validate_duplicate_booking() + + def validate_time(self): + if self.from_time and self.to_time: + if self.to_time <= self.from_time: + frappe.throw("To Time must be greater than From Time") + + def validate_date(self): + if self.booking_date: + if getdate(self.booking_date) < getdate(nowdate()): + frappe.throw("Booking Date cannot be in the past") + + def validate_room_availability(self): + """ + Ensure booking is within room's available time window + """ + + if not self.meeting_room: + return + + room = frappe.get_doc("Room", self.meeting_room) + + from_time = get_time(self.from_time) + to_time = get_time(self.to_time) + available_from = get_time(room.availability_from) + available_to = get_time(room.availability_to) + + if from_time < available_from or to_time > available_to: + frappe.throw( + f"Booking not permitted outside room availability hours " + f"({available_from} - {available_to})" + ) + + def validate_duplicate_booking(self): + """ + Prevent overlapping bookings for the same room. + + Overlap condition: + Existing.from_time < New.to_time AND + Existing.to_time > New.from_time + """ + + if not (self.meeting_room and self.booking_date and self.from_time and self.to_time): + return + + from_time = get_time(self.from_time) + to_time = get_time(self.to_time) + + overlapping_bookings = frappe.db.sql(""" + SELECT name, from_time, to_time + FROM `tabMeeting Room Booking` + WHERE + meeting_room = %s + AND booking_date = %s + AND name != %s + AND docstatus != 2 + AND from_time < %s + AND to_time > %s + """, ( + self.meeting_room, + self.booking_date, + self.name or "", + to_time, + from_time + ), as_dict=True) + + if overlapping_bookings: + booking = overlapping_bookings[0] + + frappe.throw( + f"Room already booked from {booking.from_time} to {booking.to_time} for this day" + ) + diff --git a/gams/general_administration_management_system/doctype/meeting_room_booking/test_meeting_room_booking.py b/gams/general_administration_management_system/doctype/meeting_room_booking/test_meeting_room_booking.py new file mode 100644 index 0000000..f2dbf5f --- /dev/null +++ b/gams/general_administration_management_system/doctype/meeting_room_booking/test_meeting_room_booking.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, efeone and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestMeetingRoomBooking(FrappeTestCase): + pass diff --git a/gams/general_administration_management_system/doctype/resource_booking_attendee/__init__.py b/gams/general_administration_management_system/doctype/resource_booking_attendee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.json b/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.json new file mode 100644 index 0000000..8f90688 --- /dev/null +++ b/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.json @@ -0,0 +1,36 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2026-04-08 13:43:41.270091", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2026-04-08 13:43:41.270091", + "modified_by": "admin@example.com", + "module": "General Administration Management System", + "name": "Resource Booking Attendee", + "owner": "admin@example.com", + "permissions": [], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.py b/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.py new file mode 100644 index 0000000..f6d5663 --- /dev/null +++ b/gams/general_administration_management_system/doctype/resource_booking_attendee/resource_booking_attendee.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, efeone and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ResourceBookingAttendee(Document): + pass diff --git a/gams/general_administration_management_system/doctype/room/__init__.py b/gams/general_administration_management_system/doctype/room/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gams/general_administration_management_system/doctype/room/room.js b/gams/general_administration_management_system/doctype/room/room.js new file mode 100644 index 0000000..3a69209 --- /dev/null +++ b/gams/general_administration_management_system/doctype/room/room.js @@ -0,0 +1,8 @@ +// Copyright (c) 2026, efeone and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Room", { +// refresh(frm) { + +// }, +// }); diff --git a/gams/general_administration_management_system/doctype/room/room.json b/gams/general_administration_management_system/doctype/room/room.json new file mode 100644 index 0000000..6374873 --- /dev/null +++ b/gams/general_administration_management_system/doctype/room/room.json @@ -0,0 +1,110 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:room_name", + "creation": "2026-04-08 13:42:14.488622", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "room_name", + "location", + "capacity", + "column_break_iike", + "availability_from", + "availability_to", + "status", + "section_break_wqxh", + "description", + "section_break_zurm", + "image" + ], + "fields": [ + { + "fieldname": "room_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Room Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "location", + "fieldtype": "Data", + "label": "Location" + }, + { + "fieldname": "capacity", + "fieldtype": "Int", + "label": "Capacity" + }, + { + "fieldname": "column_break_iike", + "fieldtype": "Column Break" + }, + { + "default": "08:00:00", + "fieldname": "availability_from", + "fieldtype": "Time", + "label": "Availability From" + }, + { + "default": "18:00:00", + "fieldname": "availability_to", + "fieldtype": "Time", + "label": "Availability To" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Active\nInactive\nUnder Maintenance" + }, + { + "fieldname": "section_break_wqxh", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "options": "Fields: description (Small Text), image (Attach Image)" + }, + { + "fieldname": "section_break_zurm", + "fieldtype": "Section Break" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "label": "Image" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2026-04-08 13:52:50.547657", + "modified_by": "admin@example.com", + "module": "General Administration Management System", + "name": "Room", + "naming_rule": "By fieldname", + "owner": "admin@example.com", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/gams/general_administration_management_system/doctype/room/room.py b/gams/general_administration_management_system/doctype/room/room.py new file mode 100644 index 0000000..529c036 --- /dev/null +++ b/gams/general_administration_management_system/doctype/room/room.py @@ -0,0 +1,18 @@ +# Copyright (c) 2026, efeone and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + +class Room(Document): + + def validate(self): + self.validate_time_range() + + def validate_time_range(self): + """ + Ensure availability_from is earlier than availability_to. + """ + if self.availability_from and self.availability_to: + if self.availability_from >= self.availability_to: + frappe.throw("Available From must be earlier than Available To") diff --git a/gams/general_administration_management_system/doctype/room/test_room.py b/gams/general_administration_management_system/doctype/room/test_room.py new file mode 100644 index 0000000..8ea1af6 --- /dev/null +++ b/gams/general_administration_management_system/doctype/room/test_room.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, efeone and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestRoom(FrappeTestCase): + pass