diff --git a/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs index 2b965bf..4a8d197 100644 --- a/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs +++ b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs @@ -116,7 +116,7 @@ namespace PSTW_CentralSystem.Areas.Identity.Pages.Account var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); if (result.Succeeded) { - _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); + _logger.LogInformation("{CompanyName} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); return LocalRedirect(returnUrl); } if (result.IsLockedOut) @@ -163,7 +163,7 @@ namespace PSTW_CentralSystem.Areas.Identity.Pages.Account result = await _userManager.AddLoginAsync(user, info); if (result.Succeeded) { - _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); + _logger.LogInformation("User created an account using {CompanyName} provider.", info.LoginProvider); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); diff --git a/Areas/Inventory/Models/CompanyModel.cs b/Areas/Inventory/Models/CompanyModel.cs index d898041..c02b79a 100644 --- a/Areas/Inventory/Models/CompanyModel.cs +++ b/Areas/Inventory/Models/CompanyModel.cs @@ -7,7 +7,7 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models { [Key] public int CompanyId { get; set; } - public required string Name { get; set; } + public required string CompanyName { get; set; } public virtual ICollection? Departments { get; set; } } } diff --git a/Areas/Inventory/Models/DepartmentModel.cs b/Areas/Inventory/Models/DepartmentModel.cs index a9f2fb0..a5cca0a 100644 --- a/Areas/Inventory/Models/DepartmentModel.cs +++ b/Areas/Inventory/Models/DepartmentModel.cs @@ -7,8 +7,9 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models { [Key] public int DepartmentId { get; set; } - public required string Name { get; set; } + public required string DepartmentName { get; set; } public required int CompanyId { get; set; } + [ForeignKey("CompanyId")] public virtual CompanyModel? Company { get; set; } } diff --git a/Areas/Inventory/Models/ItemModel.cs b/Areas/Inventory/Models/ItemModel.cs index 32aa1a8..a6c84a6 100644 --- a/Areas/Inventory/Models/ItemModel.cs +++ b/Areas/Inventory/Models/ItemModel.cs @@ -7,8 +7,7 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models { [Key] public int ItemID { get; set; } - [Required] - public string? UniqueID { get; set; } + public string UniqueID { get; set; } = string.Empty; public required int CompanyId { get; set; } public required int DepartmentId { get; set; } public required int ProductId { get; set; } diff --git a/Areas/Inventory/Views/Item/ItemRegistration.cshtml b/Areas/Inventory/Views/Item/ItemRegistration.cshtml index 2238476..d51ea1c 100644 --- a/Areas/Inventory/Views/Item/ItemRegistration.cshtml +++ b/Areas/Inventory/Views/Item/ItemRegistration.cshtml @@ -293,9 +293,6 @@ supplierName: "Ms Kim", }, ], - showOtherCompany: false, - showOtherDept: false, - showOtherSupplier: false, isModalOpen: false, selectedProduct: "", selectedSupplier: "", @@ -328,7 +325,7 @@ return product ? product : {}; }, showSerialNumber() { - return this.showProduct.category === 'Item' || this.showProduct.category === 'Part'; + return this.showProduct.category === 'Asset' || this.showProduct.category === 'Part'; }, }, methods: { @@ -421,8 +418,8 @@ async fetchCompanies() { try { - const response = await fetch('/api/Item/GetCompanies', { - method: 'GET', // Specify the HTTP method + const response = await fetch('/InvMainAPI/CompanyDepartmentList', { + method: 'POST', // Specify the HTTP method headers: { 'Content-Type': 'application/json' } @@ -440,19 +437,19 @@ async fetchSuppliers() { try { - const response = await fetch('/api/Item/GetSupplies', { - method: 'GET', // Specify the HTTP method + const response = await fetch('/InvMainAPI/SupplierList', { + method: 'POST', // Specify the HTTP method headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { - throw new Error('Failed to fetch supplies'); + throw new Error('Failed to fetch suppliers'); } - this.supplies = await response.json(); // Get the full response object + this.suppliers = await response.json(); // Get the full response object } catch (error) { - console.error('Error fetching supplies:', error); + console.error('Error fetching suppliers:', error); } }, async fetchCurrencyData() { @@ -469,7 +466,7 @@ console.log('Selected currency:', this.currency); }, resetForm() { - this.company = null; + this.company = ''; this.Dept = null; this.productName = null; this.imageProduct = null; @@ -486,11 +483,10 @@ this.warranty = null; this.EndWDate = null; this.invoiceDate = null; - this.showOtherCompany = false; - this.showOtherDept = false; - this.showOtherSupplier = false; - this.selectedCompany = null; - this.selectedSupplier = null; + this.selectedProduct = ""; + this.selectedSupplier = ""; + this.selectedCompany = ""; + this.selectedDepartment = ""; }, // FRONT END FUNCTIONS diff --git a/Areas/Inventory/Views/Item/ProductRegistration.cshtml b/Areas/Inventory/Views/Item/ProductRegistration.cshtml index ea7eb44..e0ac1b3 100644 --- a/Areas/Inventory/Views/Item/ProductRegistration.cshtml +++ b/Areas/Inventory/Views/Item/ProductRegistration.cshtml @@ -117,7 +117,7 @@ manufacturer: '', manufacturers: null, category: '', - categories: ["Item", "Part", "Disposable"], + categories: ["Asset", "Part", "Disposable"], modelNo: null, imageProduct: null, manufactures: [], diff --git a/Controllers/API/AdminAPI.cs b/Controllers/API/AdminAPI.cs index 40fbaa5..169a812 100644 --- a/Controllers/API/AdminAPI.cs +++ b/Controllers/API/AdminAPI.cs @@ -27,7 +27,7 @@ namespace PSTW_CentralSystem.Controllers.API var assembly = Assembly.GetExecutingAssembly(); // Get all types in the assembly (controllers will typically be in the "Controllers" namespace) - //var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && type.Name.Contains("Controller") && type.Name != "AdminController") .ToList()); + //var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && type.CompanyName.Contains("Controller") && type.CompanyName != "AdminController") .ToList()); var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && type.Name.Contains(moduleName)).FirstOrDefault()); // Iterate over the controller types and get their methods @@ -54,7 +54,7 @@ namespace PSTW_CentralSystem.Controllers.API var assembly = Assembly.GetExecutingAssembly(); // Get all types in the assembly (controllers will typically be in the "Controllers" namespace) - //var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && type.Name.Contains("Controller") && type.Name != "AdminController") .ToList()); + //var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && type.CompanyName.Contains("Controller") && type.CompanyName != "AdminController") .ToList()); var controllerTypes = await Task.Run(() => assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type) && type.IsClass && !type.Name.Contains("API") && !type.Name.Contains("Admin")).ToList()); // Iterate over the controller types and get their methods diff --git a/Controllers/API/Inventory/InvMainAPI.cs b/Controllers/API/Inventory/InvMainAPI.cs index df46aff..e925361 100644 --- a/Controllers/API/Inventory/InvMainAPI.cs +++ b/Controllers/API/Inventory/InvMainAPI.cs @@ -2,9 +2,11 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Mono.TextTemplating; +using Newtonsoft.Json; using PSTW_CentralSystem.Areas.Inventory.Models; using PSTW_CentralSystem.DBContext; using PSTW_CentralSystem.Models; +using System.ComponentModel.Design; using System.Diagnostics; using System.Reflection; @@ -24,6 +26,49 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory _authDbContext = authDbContext; } + public class DepartmentCompany + { + public int DepartmentId { get; set; } + public string? DepartmentName { get; set; } + public int CompanyId { get; set; } + public string? CompanyName { get; set; } + } + + public async Task> GetDepartmentWithCompanyList() + { + var departmentList = await _authDbContext.Departments.ToListAsync(); + var companyList = await _authDbContext.Companies.ToListAsync(); + + // Create a new list to store departments with their company name + var departmentWithCompanyList = departmentList.Select(department => new DepartmentCompany + { + DepartmentId = department.DepartmentId, + DepartmentName = department.DepartmentName, + CompanyId = department.CompanyId, + CompanyName = companyList.FirstOrDefault(company => company.CompanyId == department.CompanyId)?.CompanyName + }).ToList(); + + // Return the constructed list as JSON + return departmentWithCompanyList; + } + public async Task GetDepartmentWithCompany(int companyId, int departmentId) + { + var departmentList = await _authDbContext.Departments.FirstOrDefaultAsync(d => d.DepartmentId == departmentId ); + var companyList = await _authDbContext.Companies.FirstOrDefaultAsync(c => c.CompanyId == companyId); + + // Create a new list to store departments with their company name + var departmentWithCompany = new DepartmentCompany + { + DepartmentId = departmentList.DepartmentId, + DepartmentName = departmentList.DepartmentName, + CompanyId = departmentList.CompanyId, + CompanyName = companyList?.CompanyName + }; + + // Return the constructed list as JSON + return departmentWithCompany; + } + #region Manufacturer [HttpPost("ManufacturerList")] @@ -151,14 +196,55 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory #endregion Product #region Company + [HttpPost("CompanyDepartmentList")] public async Task CompanyDepartmentList() { - var productList = await _authDbContext.Companies.Include("Departments").ToListAsync(); - return Json(productList); + var companyList = await _authDbContext.Companies + .Include(c => c.Departments) + .Select(c => new { c.CompanyId, c.CompanyName, Departments = c.Departments + .OrderBy(d => d.DepartmentId) + .Select(d => new { d.DepartmentId, d.DepartmentName }) + }) + .ToListAsync(); + return Json(companyList); } + #endregion Company + #region Department + + [HttpPost("DepartmentCompanyList")] + public async Task DepartmentCompanyList() + { + var itemDepartment = await _authDbContext.Departments + .Include(d => d.Company) // Include the related Company entity + .Select(d => new + { + d.DepartmentId, + d.DepartmentName, + d.CompanyId, + d.Company.CompanyName, + }) + .ToListAsync(); + + //return Json(await GetDepartmentWithCompanyList()); + return Json(itemDepartment); + } + + #endregion Department + + #region Supplier + + [HttpPost("SupplierList")] + public async Task SupplierList() + { + var supplierList = await _authDbContext.Suppliers.ToListAsync(); + return Json(supplierList); + } + + #endregion Supplier + #region Item [HttpPost("ItemList")] @@ -181,13 +267,43 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory } try - { var itemDepartment = _authDbContext.Departments.Include(d => d.Company).Where(d => d.DepartmentId == item.DepartmentId).FirstOrDefault(); - string conpanyInitial = itemDepartment.Company.Name.ToString().Substring(0, 1).ToUpper(); - var uniqueId = Guid.NewGuid().ToString(); - //_authDbContext.Items.Add(item); - //await _authDbContext.SaveChangesAsync(); - var updatedList = await _authDbContext.Items.ToListAsync(); - return Json(updatedList); + { + var companyDepartment = await GetDepartmentWithCompany(item.CompanyId, item.DepartmentId); + var itemProduct = _authDbContext.Products.Where(p => p.ProductId == item.ProductId).FirstOrDefault(); + string? companyInitial = companyDepartment?.CompanyName?.ToString().Substring(0, 1).ToUpper(); + string? depatmentInitial = companyDepartment?.DepartmentName?.ToString().Substring(0, 1).ToUpper(); + char? initialCategory = itemProduct?.Category.ToString().Substring(0, 1).ToUpper().FirstOrDefault(); + string? productId = itemProduct?.ProductId.ToString("D3"); + string? itemId = item?.ItemID+1.ToString("D5"); + var uniqueId = companyInitial+depatmentInitial+initialCategory+productId+itemId?.ToUpper(); + _authDbContext.Items.Add(item); + await _authDbContext.SaveChangesAsync(); + var updatedItem = await _authDbContext.Items.Select(i => new + { + i.ItemID, + i.UniqueID, + i.CompanyId, + i.DepartmentId, + i.ProductId, + i.SerialNumber, + i.Quantity, + i.Supplier, + i.PurchaseDate, + i.PONo, + i.Currency, + i.PriceInRM, + i.CurrencyRate, + i.ConvertPrice, + i.DODate, + i.Warranty, + i.EndWDate, + i.InvoiceDate, + }).FirstOrDefaultAsync(i => i.ItemID == item.ItemID); + + updatedItem.UniqueID = uniqueId; + _authDbContext.Items.Add(item); + await _authDbContext.SaveChangesAsync(); + return Json(updatedItem); } catch (Exception ex) { diff --git a/Migrations/20241126071458_Initiate.Designer.cs b/Migrations/20241126071458_Initiate.Designer.cs deleted file mode 100644 index feff262..0000000 --- a/Migrations/20241126071458_Initiate.Designer.cs +++ /dev/null @@ -1,631 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using PSTW_CentralSystem.DBContext; - -#nullable disable - -namespace PSTW_CentralSystem.Migrations -{ - [DbContext(typeof(AuthDBContext))] - [Migration("20241126071458_Initiate")] - partial class Initiate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("longtext"); - - b.Property("ClaimValue") - .HasColumnType("longtext"); - - b.Property("RoleId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("longtext"); - - b.Property("ClaimValue") - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("varchar(255)"); - - b.Property("ProviderKey") - .HasColumnType("varchar(255)"); - - b.Property("ProviderDisplayName") - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("int"); - - b.Property("RoleId") - .HasColumnType("int"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - - b.HasData( - new - { - UserId = 1, - RoleId = 1 - }); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("int"); - - b.Property("LoginProvider") - .HasColumnType("varchar(255)"); - - b.Property("Name") - .HasColumnType("varchar(255)"); - - b.Property("Value") - .HasColumnType("longtext"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.CompanyModel", b => - { - b.Property("CompanyId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CompanyId")); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("CompanyId"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.DepartmentModel", b => - { - b.Property("DepartmentId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("DepartmentId")); - - b.Property("CompanyId") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("DepartmentId"); - - b.HasIndex("CompanyId"); - - b.ToTable("Departments"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.ItemModel", b => - { - b.Property("ItemID") - .HasColumnType("varchar(255)"); - - b.Property("CompanyId") - .HasColumnType("int"); - - b.Property("ConvertPrice") - .HasColumnType("float"); - - b.Property("Currency") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CurrencyRate") - .HasColumnType("float"); - - b.Property("DODate") - .HasColumnType("datetime(6)"); - - b.Property("DepartmentId") - .HasColumnType("int"); - - b.Property("EndWDate") - .HasColumnType("datetime(6)"); - - b.Property("InvoiceDate") - .HasColumnType("datetime(6)"); - - b.Property("PONo") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("PriceInRM") - .HasColumnType("float"); - - b.Property("ProductId") - .HasColumnType("int"); - - b.Property("PurchaseDate") - .HasColumnType("datetime(6)"); - - b.Property("Quantity") - .HasColumnType("int"); - - b.Property("SerialNumber") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Supplier") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Warranty") - .HasColumnType("int"); - - b.HasKey("ItemID"); - - b.HasIndex("CompanyId"); - - b.HasIndex("DepartmentId"); - - b.HasIndex("ProductId"); - - b.ToTable("Items"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.ManufacturerModel", b => - { - b.Property("ManufacturerId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ManufacturerId")); - - b.Property("ManufacturerName") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("ManufacturerId"); - - b.ToTable("Manufacturers"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.ProductModel", b => - { - b.Property("ProductId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("ProductId")); - - b.Property("Category") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ImageProduct") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ManufacturerId") - .HasColumnType("int"); - - b.Property("ModelNo") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ProductName") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("QuantityProduct") - .HasColumnType("int"); - - b.HasKey("ProductId"); - - b.HasIndex("ManufacturerId"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.SupplierModel", b => - { - b.Property("SupplierId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SupplierId")); - - b.Property("SupplierEmail") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("SupplierGender") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("SupplierName") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("SupplierPhoneNo") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("SupplierId"); - - b.ToTable("Suppliers"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Models.ModuleSettingModel", b => - { - b.Property("SettingId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("SettingId")); - - b.Property("AllowedUserType") - .HasColumnType("longtext"); - - b.Property("Description") - .HasColumnType("longtext"); - - b.Property("MethodAllowedUserType") - .HasColumnType("json"); - - b.Property("ModuleName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("varchar(50)"); - - b.Property("ModuleStatus") - .HasColumnType("int"); - - b.HasKey("SettingId"); - - b.ToTable("ModuleSettings"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Models.RoleModel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("longtext"); - - b.Property("Description") - .HasColumnType("longtext"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - - b.HasData( - new - { - Id = 1, - Description = "Can access all pages", - Name = "SuperAdmin", - NormalizedName = "SUPERADMIN" - }, - new - { - Id = 2, - Description = "Can access some admin pages", - Name = "SystemAdmin", - NormalizedName = "SYSTEMADMIN" - }, - new - { - Id = 3, - Description = "Can access operation pages", - Name = "Engineer", - NormalizedName = "ENGINEER" - }, - new - { - Id = 4, - Description = "Can access data viewer pages", - Name = "Observer", - NormalizedName = "OBSERVER" - }); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Models.UserModel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("longtext"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("tinyint(1)"); - - b.Property("FullName") - .HasColumnType("longtext"); - - b.Property("LockoutEnabled") - .HasColumnType("tinyint(1)"); - - b.Property("LockoutEnd") - .HasColumnType("datetime(6)"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("longtext"); - - b.Property("PhoneNumber") - .HasColumnType("longtext"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("tinyint(1)"); - - b.Property("SecurityStamp") - .HasColumnType("longtext"); - - b.Property("TwoFactorEnabled") - .HasColumnType("tinyint(1)"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("UserStatus") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasData( - new - { - Id = 1, - AccessFailedCount = 0, - ConcurrencyStamp = "dde44c98-793e-452c-8123-5252dc03d655", - Email = "admin@pstw.com.my", - EmailConfirmed = true, - FullName = "MAAdmin", - LockoutEnabled = false, - NormalizedEmail = "ADMIN@PSTW.COM.MY", - NormalizedUserName = "ADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAEF/vIsmJIWgsCX1cyJiM/miWN66l6UKVbXIY07eBwo/kOy6xL5olLByKrgW7MdbadQ==", - PhoneNumberConfirmed = false, - SecurityStamp = "1e63fa4d-6a8a-4738-9036-7b51d02e1eaf", - TwoFactorEnabled = false, - UserName = "admin@pstw.com.my" - }, - new - { - Id = 2, - AccessFailedCount = 0, - ConcurrencyStamp = "b529f4f9-3426-4a74-b048-d8995fe3e647", - Email = "sysadmin@pstw.com.my", - EmailConfirmed = true, - FullName = "SysAdmin", - LockoutEnabled = false, - NormalizedEmail = "SYSADMIN@PSTW.COM.MY", - NormalizedUserName = "SYSADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAEIEYpwwMbS9j2l6V3fpUQONaKxCMJN3pV8rVeN3eo0iva0Bu9Jj1NIdkS4GnzvpDVw==", - PhoneNumberConfirmed = false, - SecurityStamp = "065ee938-093c-4816-ad28-f2e0831a7550", - TwoFactorEnabled = false, - UserName = "sysadmin@pstw.com.my" - }); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("PSTW_CentralSystem.Models.RoleModel", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("PSTW_CentralSystem.Models.UserModel", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("PSTW_CentralSystem.Models.UserModel", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("PSTW_CentralSystem.Models.RoleModel", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("PSTW_CentralSystem.Models.UserModel", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("PSTW_CentralSystem.Models.UserModel", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.DepartmentModel", b => - { - b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.CompanyModel", "Company") - .WithMany() - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.ItemModel", b => - { - b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.CompanyModel", "Company") - .WithMany() - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.DepartmentModel", "Department") - .WithMany() - .HasForeignKey("DepartmentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.ProductModel", "Product") - .WithMany() - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - - b.Navigation("Department"); - - b.Navigation("Product"); - }); - - modelBuilder.Entity("PSTW_CentralSystem.Areas.Inventory.Models.ProductModel", b => - { - b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.ManufacturerModel", "Manufacturer") - .WithMany() - .HasForeignKey("ManufacturerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Manufacturer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Migrations/20241127081155_UpdateItemModel.cs b/Migrations/20241127081155_UpdateItemModel.cs deleted file mode 100644 index 4bbd63b..0000000 --- a/Migrations/20241127081155_UpdateItemModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace PSTW_CentralSystem.Migrations -{ - /// - public partial class UpdateItemModel : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "QuantityProduct", - table: "Products", - type: "int", - nullable: true, - oldClrType: typeof(int), - oldType: "int"); - - migrationBuilder.AlterColumn( - name: "ItemID", - table: "Items", - type: "int", - nullable: false, - oldClrType: typeof(string), - oldType: "varchar(255)") - .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn) - .OldAnnotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.AddColumn( - name: "UniqueID", - table: "Items", - type: "longtext", - nullable: false) - .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.UpdateData( - table: "AspNetUsers", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, - values: new object[] { "a04422b5-87e6-445c-9640-91f82b7ab925", "AQAAAAIAAYagAAAAECKZDbSSf9PVXnc5gc11ayb/qGSLIVi595BpbLpr4GeEDCONiPEEGeKOF7Hmt2evRA==", "a9620efc-3783-46d6-a769-9fa7b0cca19b" }); - - migrationBuilder.UpdateData( - table: "AspNetUsers", - keyColumn: "Id", - keyValue: 2, - columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, - values: new object[] { "608f450b-bfc3-4e15-a815-c9f25e0b92f4", "AQAAAAIAAYagAAAAED3P9MR4LRFRqUDqLBBwF3lpXLNrOzcoEkMjr2SJSU2u2z0mvwXXGtZO3wPy8PcwQQ==", "0282ae31-790c-403a-8f42-3596b2d1e454" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "UniqueID", - table: "Items"); - - migrationBuilder.AlterColumn( - name: "QuantityProduct", - table: "Products", - type: "int", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ItemID", - table: "Items", - type: "varchar(255)", - nullable: false, - oldClrType: typeof(int), - oldType: "int") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn); - - migrationBuilder.UpdateData( - table: "AspNetUsers", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, - values: new object[] { "dde44c98-793e-452c-8123-5252dc03d655", "AQAAAAIAAYagAAAAEF/vIsmJIWgsCX1cyJiM/miWN66l6UKVbXIY07eBwo/kOy6xL5olLByKrgW7MdbadQ==", "1e63fa4d-6a8a-4738-9036-7b51d02e1eaf" }); - - migrationBuilder.UpdateData( - table: "AspNetUsers", - keyColumn: "Id", - keyValue: 2, - columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, - values: new object[] { "b529f4f9-3426-4a74-b048-d8995fe3e647", "AQAAAAIAAYagAAAAEIEYpwwMbS9j2l6V3fpUQONaKxCMJN3pV8rVeN3eo0iva0Bu9Jj1NIdkS4GnzvpDVw==", "065ee938-093c-4816-ad28-f2e0831a7550" }); - } - } -} diff --git a/Migrations/20241127081155_UpdateItemModel.Designer.cs b/Migrations/20241128045608_Initiate.Designer.cs similarity index 96% rename from Migrations/20241127081155_UpdateItemModel.Designer.cs rename to Migrations/20241128045608_Initiate.Designer.cs index 0de93af..3723cdb 100644 --- a/Migrations/20241127081155_UpdateItemModel.Designer.cs +++ b/Migrations/20241128045608_Initiate.Designer.cs @@ -12,8 +12,8 @@ using PSTW_CentralSystem.DBContext; namespace PSTW_CentralSystem.Migrations { [DbContext(typeof(AuthDBContext))] - [Migration("20241127081155_UpdateItemModel")] - partial class UpdateItemModel + [Migration("20241128045608_Initiate")] + partial class Initiate { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -143,7 +143,7 @@ namespace PSTW_CentralSystem.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CompanyId")); - b.Property("Name") + b.Property("CompanyName") .IsRequired() .HasColumnType("longtext"); @@ -163,7 +163,7 @@ namespace PSTW_CentralSystem.Migrations b.Property("CompanyId") .HasColumnType("int"); - b.Property("Name") + b.Property("DepartmentName") .IsRequired() .HasColumnType("longtext"); @@ -501,16 +501,16 @@ namespace PSTW_CentralSystem.Migrations { Id = 1, AccessFailedCount = 0, - ConcurrencyStamp = "a04422b5-87e6-445c-9640-91f82b7ab925", + ConcurrencyStamp = "df01136b-c869-4bc3-9512-34a9cdc8f73d", Email = "admin@pstw.com.my", EmailConfirmed = true, FullName = "MAAdmin", LockoutEnabled = false, NormalizedEmail = "ADMIN@PSTW.COM.MY", NormalizedUserName = "ADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAECKZDbSSf9PVXnc5gc11ayb/qGSLIVi595BpbLpr4GeEDCONiPEEGeKOF7Hmt2evRA==", + PasswordHash = "AQAAAAIAAYagAAAAECcU3fIsIpqE1gECPg262gMejQiypGUXipVbiRtF66ywBqUHdohCj89hiJAafOlrPQ==", PhoneNumberConfirmed = false, - SecurityStamp = "a9620efc-3783-46d6-a769-9fa7b0cca19b", + SecurityStamp = "d36451ff-cfab-46e1-bf80-6b428d79a19b", TwoFactorEnabled = false, UserName = "admin@pstw.com.my" }, @@ -518,16 +518,16 @@ namespace PSTW_CentralSystem.Migrations { Id = 2, AccessFailedCount = 0, - ConcurrencyStamp = "608f450b-bfc3-4e15-a815-c9f25e0b92f4", + ConcurrencyStamp = "6f7244cf-e611-4088-890a-72939cfbefa5", Email = "sysadmin@pstw.com.my", EmailConfirmed = true, FullName = "SysAdmin", LockoutEnabled = false, NormalizedEmail = "SYSADMIN@PSTW.COM.MY", NormalizedUserName = "SYSADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAED3P9MR4LRFRqUDqLBBwF3lpXLNrOzcoEkMjr2SJSU2u2z0mvwXXGtZO3wPy8PcwQQ==", + PasswordHash = "AQAAAAIAAYagAAAAEJwGvD0ionYUADG6FQvuXiK0/897GSnJ8z55w1P0GaItbNjjypF1+aDuRViCZMUQ+g==", PhoneNumberConfirmed = false, - SecurityStamp = "0282ae31-790c-403a-8f42-3596b2d1e454", + SecurityStamp = "50df1ec2-4ba7-4eb5-84a3-ffb35b44f391", TwoFactorEnabled = false, UserName = "sysadmin@pstw.com.my" }); diff --git a/Migrations/20241126071458_Initiate.cs b/Migrations/20241128045608_Initiate.cs similarity index 95% rename from Migrations/20241126071458_Initiate.cs rename to Migrations/20241128045608_Initiate.cs index c39f312..14f5c75 100644 --- a/Migrations/20241126071458_Initiate.cs +++ b/Migrations/20241128045608_Initiate.cs @@ -82,7 +82,7 @@ namespace PSTW_CentralSystem.Migrations { CompanyId = table.Column(type: "int", nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: false) + CompanyName = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, constraints: table => @@ -276,7 +276,7 @@ namespace PSTW_CentralSystem.Migrations { DepartmentId = table.Column(type: "int", nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), - Name = table.Column(type: "longtext", nullable: false) + DepartmentName = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), CompanyId = table.Column(type: "int", nullable: false) }, @@ -305,7 +305,7 @@ namespace PSTW_CentralSystem.Migrations .Annotation("MySql:CharSet", "utf8mb4"), ModelNo = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - QuantityProduct = table.Column(type: "int", nullable: false), + QuantityProduct = table.Column(type: "int", nullable: true), ImageProduct = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4") }, @@ -325,7 +325,9 @@ namespace PSTW_CentralSystem.Migrations name: "Items", columns: table => new { - ItemID = table.Column(type: "varchar(255)", nullable: false) + ItemID = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UniqueID = table.Column(type: "longtext", nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), CompanyId = table.Column(type: "int", nullable: false), DepartmentId = table.Column(type: "int", nullable: false), @@ -388,8 +390,8 @@ namespace PSTW_CentralSystem.Migrations columns: new[] { "Id", "AccessFailedCount", "ConcurrencyStamp", "Email", "EmailConfirmed", "FullName", "LockoutEnabled", "LockoutEnd", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", "PhoneNumberConfirmed", "SecurityStamp", "TwoFactorEnabled", "UserName", "UserStatus" }, values: new object[,] { - { 1, 0, "dde44c98-793e-452c-8123-5252dc03d655", "admin@pstw.com.my", true, "MAAdmin", false, null, "ADMIN@PSTW.COM.MY", "ADMIN@PSTW.COM.MY", "AQAAAAIAAYagAAAAEF/vIsmJIWgsCX1cyJiM/miWN66l6UKVbXIY07eBwo/kOy6xL5olLByKrgW7MdbadQ==", null, false, "1e63fa4d-6a8a-4738-9036-7b51d02e1eaf", false, "admin@pstw.com.my", null }, - { 2, 0, "b529f4f9-3426-4a74-b048-d8995fe3e647", "sysadmin@pstw.com.my", true, "SysAdmin", false, null, "SYSADMIN@PSTW.COM.MY", "SYSADMIN@PSTW.COM.MY", "AQAAAAIAAYagAAAAEIEYpwwMbS9j2l6V3fpUQONaKxCMJN3pV8rVeN3eo0iva0Bu9Jj1NIdkS4GnzvpDVw==", null, false, "065ee938-093c-4816-ad28-f2e0831a7550", false, "sysadmin@pstw.com.my", null } + { 1, 0, "df01136b-c869-4bc3-9512-34a9cdc8f73d", "admin@pstw.com.my", true, "MAAdmin", false, null, "ADMIN@PSTW.COM.MY", "ADMIN@PSTW.COM.MY", "AQAAAAIAAYagAAAAECcU3fIsIpqE1gECPg262gMejQiypGUXipVbiRtF66ywBqUHdohCj89hiJAafOlrPQ==", null, false, "d36451ff-cfab-46e1-bf80-6b428d79a19b", false, "admin@pstw.com.my", null }, + { 2, 0, "6f7244cf-e611-4088-890a-72939cfbefa5", "sysadmin@pstw.com.my", true, "SysAdmin", false, null, "SYSADMIN@PSTW.COM.MY", "SYSADMIN@PSTW.COM.MY", "AQAAAAIAAYagAAAAEJwGvD0ionYUADG6FQvuXiK0/897GSnJ8z55w1P0GaItbNjjypF1+aDuRViCZMUQ+g==", null, false, "50df1ec2-4ba7-4eb5-84a3-ffb35b44f391", false, "sysadmin@pstw.com.my", null } }); migrationBuilder.InsertData( diff --git a/Migrations/AuthDBContextModelSnapshot.cs b/Migrations/AuthDBContextModelSnapshot.cs index 6fd9ad2..817a734 100644 --- a/Migrations/AuthDBContextModelSnapshot.cs +++ b/Migrations/AuthDBContextModelSnapshot.cs @@ -140,7 +140,7 @@ namespace PSTW_CentralSystem.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CompanyId")); - b.Property("Name") + b.Property("CompanyName") .IsRequired() .HasColumnType("longtext"); @@ -160,7 +160,7 @@ namespace PSTW_CentralSystem.Migrations b.Property("CompanyId") .HasColumnType("int"); - b.Property("Name") + b.Property("DepartmentName") .IsRequired() .HasColumnType("longtext"); @@ -498,16 +498,16 @@ namespace PSTW_CentralSystem.Migrations { Id = 1, AccessFailedCount = 0, - ConcurrencyStamp = "a04422b5-87e6-445c-9640-91f82b7ab925", + ConcurrencyStamp = "df01136b-c869-4bc3-9512-34a9cdc8f73d", Email = "admin@pstw.com.my", EmailConfirmed = true, FullName = "MAAdmin", LockoutEnabled = false, NormalizedEmail = "ADMIN@PSTW.COM.MY", NormalizedUserName = "ADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAECKZDbSSf9PVXnc5gc11ayb/qGSLIVi595BpbLpr4GeEDCONiPEEGeKOF7Hmt2evRA==", + PasswordHash = "AQAAAAIAAYagAAAAECcU3fIsIpqE1gECPg262gMejQiypGUXipVbiRtF66ywBqUHdohCj89hiJAafOlrPQ==", PhoneNumberConfirmed = false, - SecurityStamp = "a9620efc-3783-46d6-a769-9fa7b0cca19b", + SecurityStamp = "d36451ff-cfab-46e1-bf80-6b428d79a19b", TwoFactorEnabled = false, UserName = "admin@pstw.com.my" }, @@ -515,16 +515,16 @@ namespace PSTW_CentralSystem.Migrations { Id = 2, AccessFailedCount = 0, - ConcurrencyStamp = "608f450b-bfc3-4e15-a815-c9f25e0b92f4", + ConcurrencyStamp = "6f7244cf-e611-4088-890a-72939cfbefa5", Email = "sysadmin@pstw.com.my", EmailConfirmed = true, FullName = "SysAdmin", LockoutEnabled = false, NormalizedEmail = "SYSADMIN@PSTW.COM.MY", NormalizedUserName = "SYSADMIN@PSTW.COM.MY", - PasswordHash = "AQAAAAIAAYagAAAAED3P9MR4LRFRqUDqLBBwF3lpXLNrOzcoEkMjr2SJSU2u2z0mvwXXGtZO3wPy8PcwQQ==", + PasswordHash = "AQAAAAIAAYagAAAAEJwGvD0ionYUADG6FQvuXiK0/897GSnJ8z55w1P0GaItbNjjypF1+aDuRViCZMUQ+g==", PhoneNumberConfirmed = false, - SecurityStamp = "0282ae31-790c-403a-8f42-3596b2d1e454", + SecurityStamp = "50df1ec2-4ba7-4eb5-84a3-ffb35b44f391", TwoFactorEnabled = false, UserName = "sysadmin@pstw.com.my" }); diff --git a/wwwroot/Media/Inventory/Images/ThermoUSB.jpg b/wwwroot/Media/Inventory/Images/ThermoUSB.jpg new file mode 100644 index 0000000..4a13fc6 Binary files /dev/null and b/wwwroot/Media/Inventory/Images/ThermoUSB.jpg differ