diff --git a/src/Modules/SimplCommerce.Module.Core/Controllers/LocationController.cs b/src/Modules/SimplCommerce.Module.Core/Controllers/LocationController.cs index 768675c6e3..40556a663f 100644 --- a/src/Modules/SimplCommerce.Module.Core/Controllers/LocationController.cs +++ b/src/Modules/SimplCommerce.Module.Core/Controllers/LocationController.cs @@ -5,6 +5,7 @@ namespace SimplCommerce.Module.Core.Controllers { + [Route("api/location")] public class LocationController : Controller { private readonly IRepository districtRepository; @@ -29,5 +30,25 @@ public IActionResult GetDistricts(long stateOrProvinceId) return Json(districts); } + + [Route("districts")] + public IActionResult Get(long? stateOrProvinceId) + { + var districts = districtRepository.Query(); + + if (stateOrProvinceId.HasValue) + { + districts = districts.Where(d => d.StateOrProvinceId == stateOrProvinceId.GetValueOrDefault()); + } + + if (!districts.Any()) + { + return NotFound(); + } + + districts = districts.OrderBy(x => x.Name); + + return Json(districts.Select(d=> new { d.Id, d.Name }).ToList()); + } } } diff --git a/src/Modules/SimplCommerce.Module.Core/Controllers/StateOrProvinceApiController.cs b/src/Modules/SimplCommerce.Module.Core/Controllers/StateOrProvinceApiController.cs index 79de2d9206..2d4f5f6573 100644 --- a/src/Modules/SimplCommerce.Module.Core/Controllers/StateOrProvinceApiController.cs +++ b/src/Modules/SimplCommerce.Module.Core/Controllers/StateOrProvinceApiController.cs @@ -40,10 +40,24 @@ public async Task GetStatesOrProvinces(long countryId) return Ok(statesOrProvinces); } + public async Task Get() + { + var statesOrProvinces = await _stateOrProvinceRepository.Query() + .OrderBy(x => x.Name) + .Select(x => new + { + x.Id, + x.Name + }) + .ToListAsync(); + + return Ok(statesOrProvinces); + } + [HttpPost("grid")] public IActionResult List(int countryId, [FromBody] SmartTableParam param) { - var query = _stateOrProvinceRepository.Query().Where(sp=> sp.CountryId == countryId); + var query = _stateOrProvinceRepository.Query().Where(sp => sp.CountryId == countryId); if (param.Search.PredicateObject != null) { @@ -58,13 +72,13 @@ public IActionResult List(int countryId, [FromBody] SmartTableParam param) var stateProvinces = query.ToSmartTableResult( param, - sp=> new - { - sp.Id, - sp.Name, - sp.Code, - sp.CountryCode - }); + sp => new + { + sp.Id, + sp.Name, + sp.Code, + sp.CountryCode + }); return Json(stateProvinces); } diff --git a/src/Modules/SimplCommerce.Module.Core/Models/Address.cs b/src/Modules/SimplCommerce.Module.Core/Models/Address.cs index 96fe3fb64b..d605fcdaf9 100644 --- a/src/Modules/SimplCommerce.Module.Core/Models/Address.cs +++ b/src/Modules/SimplCommerce.Module.Core/Models/Address.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using SimplCommerce.Infrastructure.Models; namespace SimplCommerce.Module.Core.Models @@ -21,10 +22,12 @@ public class Address : EntityBase public District District { get; set; } + [Required] public long StateOrProvinceId { get; set; } public StateOrProvince StateOrProvince { get; set; } + [Required] public long CountryId { get; set; } public Country Country { get; set; } diff --git a/src/Modules/SimplCommerce.Module.Core/Views/HomeAdmin/Index.cshtml b/src/Modules/SimplCommerce.Module.Core/Views/HomeAdmin/Index.cshtml index 1ce0fd30a3..e81a54df76 100644 --- a/src/Modules/SimplCommerce.Module.Core/Views/HomeAdmin/Index.cshtml +++ b/src/Modules/SimplCommerce.Module.Core/Views/HomeAdmin/Index.cshtml @@ -206,6 +206,9 @@ + + + @@ -291,6 +294,7 @@
  • @Localizer["Tax Classes"]
  • @Localizer["Tax Rates"]
  • @Localizer["Shipping Providers"]
  • +
  • @Localizer["Warehouses"]
  • @Localizer["Payment Providers"]
  • @Localizer["Settings"]
  • @Localizer["Translations"]
  • diff --git a/src/Modules/SimplCommerce.Module.Inventory/Controllers/WarehouseApiController.cs b/src/Modules/SimplCommerce.Module.Inventory/Controllers/WarehouseApiController.cs index 0410cfe4a9..2f4ec732e7 100644 --- a/src/Modules/SimplCommerce.Module.Inventory/Controllers/WarehouseApiController.cs +++ b/src/Modules/SimplCommerce.Module.Inventory/Controllers/WarehouseApiController.cs @@ -1,10 +1,14 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using SimplCommerce.Infrastructure.Data; +using SimplCommerce.Infrastructure.Web.SmartTable; +using SimplCommerce.Module.Core.Models; using SimplCommerce.Module.Inventory.Models; +using SimplCommerce.Module.Inventory.ViewModels; namespace SimplCommerce.Module.Inventory.Controllers { @@ -13,10 +17,12 @@ namespace SimplCommerce.Module.Inventory.Controllers public class WarehouseApiController : Controller { private readonly IRepository _warehouseRepository; + private readonly IRepository
    _addressRepository; - public WarehouseApiController(IRepository warehouseRepository) + public WarehouseApiController(IRepository warehouseRepository, IRepository
    addressRepository) { _warehouseRepository = warehouseRepository; + _addressRepository = addressRepository; } public async Task Get() @@ -29,5 +35,160 @@ public async Task Get() return Ok(warehouses); } + + [HttpPost("grid")] + public IActionResult List([FromBody] SmartTableParam param) + { + var query = _warehouseRepository.Query(); + if (param.Search.PredicateObject != null) + { + dynamic search = param.Search.PredicateObject; + + if (search.Name != null) + { + string name = $"%{search.Name}%"; + query = query.Where(x => EF.Functions.Like(x.Name, name)); + } + } + + var warehouses = query.ToSmartTableResult( + param, + sp => new + { + sp.Id, + sp.Name + }); + + return Json(warehouses); + } + + [HttpGet("{id}")] + public async Task Get(long id) + { + var warehouse = await _warehouseRepository.Query().Include(w => w.Address).FirstOrDefaultAsync(w => w.Id == id); + + if (warehouse == null) + { + return NotFound(); + + } + + var address = warehouse.Address ?? new Address(); + + var model = new WarehouseVm + { + Id = warehouse.Id, + Name = warehouse.Name, + AddressId = address.Id, + ContactName = address.ContactName, + AddressLine1 = address.AddressLine1, + AddressLine2 = address.AddressLine2, + Phone = address.Phone, + StateOrProvinceId = address.StateOrProvinceId, + CountryId = address.CountryId, + City = address.City, + DistrictId = address.DistrictId, + PostalCode = address.PostalCode + }; + + return Json(model); + } + + [HttpPost] + public async Task Post([FromBody] WarehouseVm model) + { + if (ModelState.IsValid) + { + var address = new Address + { + ContactName = model.ContactName, + AddressLine1 = model.AddressLine1, + AddressLine2 = model.AddressLine2, + Phone = model.Phone, + StateOrProvinceId = model.StateOrProvinceId, + CountryId = model.CountryId, + City = model.City, + DistrictId = model.DistrictId, + PostalCode = model.PostalCode + }; + + var warehouse = new Warehouse + { + Name = model.Name, + Address = address + }; + + _warehouseRepository.Add(warehouse); + + await _warehouseRepository.SaveChangesAsync(); + + return CreatedAtAction(nameof(Get), new { id = warehouse.Id }, null); + } + return BadRequest(ModelState); + } + + [HttpPut("{id}")] + public async Task Put(long id, [FromBody] WarehouseVm model) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var warehouse = await _warehouseRepository.Query().FirstOrDefaultAsync(x => x.Id == id); + if (warehouse == null) + { + return NotFound(); + } + + var address = await _addressRepository.Query().FirstOrDefaultAsync(a => a.Id == model.AddressId); + + if (address != null) + { + address.ContactName = model.ContactName; + address.Phone = model.Phone; + address.PostalCode = model.PostalCode; + address.StateOrProvinceId = model.StateOrProvinceId; + address.CountryId = model.CountryId; + address.DistrictId = model.DistrictId; + + await _addressRepository.SaveChangesAsync(); + } + + warehouse.Address = address; + warehouse.Name = model.Name; + + await _warehouseRepository.SaveChangesAsync(); + + return Accepted(); + } + + [HttpDelete("{id}")] + public async Task Delete(long id) + { + var warehouse = await _warehouseRepository.Query().Include(w => w.Address).FirstOrDefaultAsync(x => x.Id == id); + if (warehouse == null) + { + return NotFound(); + } + + try + { + _warehouseRepository.Remove(warehouse); + _addressRepository.Remove(warehouse.Address); + + await _warehouseRepository.SaveChangesAsync(); + } + catch (DbUpdateException) + { + return BadRequest(new { Error = $"The warehouse {warehouse.Name} can't not be deleted because it is referenced by other tables" }); + } + catch (Exception ex) + { + return BadRequest(new { Error = ex.GetBaseException().Message }); + } + + return NoContent(); + } } } diff --git a/src/Modules/SimplCommerce.Module.Inventory/Models/Warehouse.cs b/src/Modules/SimplCommerce.Module.Inventory/Models/Warehouse.cs index ea9431e1fb..54ba057bc1 100644 --- a/src/Modules/SimplCommerce.Module.Inventory/Models/Warehouse.cs +++ b/src/Modules/SimplCommerce.Module.Inventory/Models/Warehouse.cs @@ -1,10 +1,12 @@ using SimplCommerce.Infrastructure.Models; using SimplCommerce.Module.Core.Models; +using System.ComponentModel.DataAnnotations; namespace SimplCommerce.Module.Inventory.Models { public class Warehouse : EntityBase { + [Required] public string Name { get; set; } public Address Address { get; set; } diff --git a/src/Modules/SimplCommerce.Module.Inventory/ViewModels/WarehouseVm.cs b/src/Modules/SimplCommerce.Module.Inventory/ViewModels/WarehouseVm.cs new file mode 100644 index 0000000000..c1029d046d --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/ViewModels/WarehouseVm.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SimplCommerce.Module.Inventory.ViewModels +{ + public class WarehouseVm + { + public long Id { get; set; } + + [Required] + public string Name { get; set; } + + public long AddressId { get; set; } + + public string ContactName { get; set; } + + public string Phone { get; set; } + + public string AddressLine1 { get; set; } + + public string AddressLine2 { get; set; } + + public string City { get; set; } + + public string PostalCode { get; set; } + + public long? DistrictId { get; set; } + + [Required] + public long StateOrProvinceId { get; set; } + + [Required] + public long CountryId { get; set; } + + } +} diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/inventory.module.js b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/inventory.module.js index e5dbbe4728..84ab0681e6 100644 --- a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/inventory.module.js +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/inventory.module.js @@ -9,6 +9,21 @@ url: '/stocks', templateUrl: 'modules/inventory/admin/stock/stock-form.html', controller: 'StockFormCtrl as vm' + }) + .state('warehouses', { + url: '/warehouses', + templateUrl: 'modules/inventory/admin/warehouse/warehouse-list.html', + controller: 'WarehouseListCtrl as vm' + }) + .state('warehouse-create', { + url: '/warehouse/create', + templateUrl: 'modules/inventory/admin/warehouse/warehouse-form.html', + controller: 'WarehouseFormCtrl as vm' + }) + .state('warehouse-edit', { + url: '/warehouses/edit/:id', + templateUrl: 'modules/inventory/admin/warehouse/warehouse-form.html', + controller: 'WarehouseFormCtrl as vm' }); }]); })(); \ No newline at end of file diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.html b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.html new file mode 100644 index 0000000000..abe079273c --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.html @@ -0,0 +1,93 @@ +
    +
    +

    {{::vm.translate.get('Create Warehouse')}}

    +

    {{::vm.translate.get('Edit Warehouse')}}

    +
    +
    +
    +
    +
      +
    • {{error}}
    • +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.js b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.js new file mode 100644 index 0000000000..a0666b321d --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-form.js @@ -0,0 +1,108 @@ +/*global angular*/ +(function () { + angular + .module('simplAdmin.inventory') + .controller('WarehouseFormCtrl', WarehouseFormCtrl); + + /* @ngInject */ + function WarehouseFormCtrl(warehouseService, translateService, $state, $stateParams) { + var vm = this, + tableStateRef; + vm.translate = translateService; + vm.warehouseId = $stateParams.id; + vm.isEditMode = vm.warehouseId > 0; + + vm.warehouse = {}; + vm.countries = []; + vm.statesOrProvinces = []; + vm.districts = []; + + vm.getStocks = function getStocks(tableState) { + tableStateRef = tableState; + vm.isLoading = true; + stockService.getStocks(vm.selectedWarehouseId, tableState).then(function (result) { + vm.stocks = result.data.items; + tableState.pagination.numberOfPages = result.data.numberOfPages; + vm.isLoading = false; + }); + }; + + vm.save = function save() { + var promise; + if (vm.isEditMode) { + promise = warehouseService.editWarehouse(vm.warehouse); + } else { + promise = warehouseService.createWarehouse(vm.warehouse); + } + + promise + .then(function (result) { + $state.go('warehouses'); + }) + .catch(function (response) { + var error = response.data; + vm.validationErrors = []; + if (error && angular.isObject(error)) { + for (var key in error) { + vm.validationErrors.push(error[key][0]); + } + } else { + vm.validationErrors.push(translateService.get('Could not save Warehouse.')); + } + }); + }; + + getCountries = function () { + warehouseService.getCountries().then(function (result) { + vm.countries = result.data; + vm.warehouse.countryId = vm.warehouse.countryId || vm.countries[0].id.toString(); + }).catch(function (err) { }); + }; + + getStatesOrProvinces = function (countryId) { + warehouseService.getStatesOrProvinces(countryId).then(function (result) { + vm.statesOrProvinces = result.data; + vm.warehouse.stateOrProvinceId = vm.warehouse.stateOrProvinceId || vm.statesOrProvinces[0].id.toString(); + }).catch(function (err) { }); + }; + + getDistricts = function (stateOrProvinceId) { + warehouseService.getDistricts(stateOrProvinceId).then(function (result) { + vm.districts = result.data; + vm.warehouse.districtId = vm.warehouse.districtId || vm.districts[0].id.toString(); + }).catch(function (err) { }); + }; + + + vm.onStateOrProvinceSelected = function (stateOrProvinceId) { + getDistricts(stateOrProvinceId); + }; + + vm.onCountrySelected = function (countryId) { + vm.statesOrProvinces = []; + vm.districts = []; + getStatesOrProvinces(countryId); + }; + + function init() { + if (vm.isEditMode) { + warehouseService.getWarehouse(vm.warehouseId).then(function (result) { + vm.warehouse = result.data; + + getCountries(); + + getStatesOrProvinces(vm.warehouse.countryId); + + getDistricts(vm.warehouse.stateOrProvinceId); + }); + } else { + getCountries(); + } + + + } + + init(); + + } +})(); \ No newline at end of file diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.html b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.html new file mode 100644 index 0000000000..6f493fca45 --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{::vm.translate.get('Name')}} 
    +
    + +
    +
    {{warehouse.name}} + + +
    Loading ...
    \ No newline at end of file diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.js b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.js new file mode 100644 index 0000000000..e32ec383bd --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-list.js @@ -0,0 +1,39 @@ +/*global angular*/ +(function () { + angular + .module('simplAdmin.inventory') + .controller('WarehouseListCtrl', WarehouseListCtrl); + + /* @ngInject */ + function WarehouseListCtrl(warehouseService, translateService, $state) { + var vm = this, + tableStateRef; + vm.warehouses = []; + vm.translate = translateService; + + vm.getWarehouses = function (tableState) { + tableStateRef = tableState; + vm.isLoading = true; + warehouseService.getWarehouses(tableState).then(function (result) { + vm.warehouses = result.data.items; + tableState.pagination.numberOfPages = result.data.numberOfPages; + vm.isLoading = false; + }); + }; + + vm.deleteWarehouse = function (warehouse) { + bootbox.confirm('Are you sure you want to delete this warehouse: ' + warehouse.name, function (result) { + if (result) { + warehouseService.deleteWarehouse(warehouse) + .then(function (result) { + vm.getWarehouses(tableStateRef); + toastr.success(warehouse.name + ' has been deleted'); + }) + .catch(function (response) { + toastr.error(response.data.error); + }); + } + }); + }; + } +})(); \ No newline at end of file diff --git a/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-service.js b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-service.js new file mode 100644 index 0000000000..87f1c43d5e --- /dev/null +++ b/src/Modules/SimplCommerce.Module.Inventory/wwwroot/admin/warehouse/warehouse-service.js @@ -0,0 +1,59 @@ +/*global angular*/ +(function () { + angular + .module('simplAdmin.inventory') + .factory('warehouseService', warehouseService); + + /* @ngInject */ + function warehouseService($http) { + var service = { + getWarehouses: getWarehouses, + getWarehouse: getWarehouse, + getCountries: getCountries, + getStatesOrProvinces: getStatesOrProvinces, + getDistricts: getDistricts, + editWarehouse: editWarehouse, + createWarehouse: createWarehouse, + deleteWarehouse: deleteWarehouse + }; + return service; + + function getWarehouses(params) { + return $http.post('api/warehouses/grid', params); + } + + function getCountries() { + return $http.get('api/countries'); + } + + function getStatesOrProvinces(countryId) { + if (countryId) + return $http.get('api/countries/' + countryId + '/states-provinces'); + + return $http.get('api/states-provinces'); + } + + function getDistricts(stateOrProvinceId) { + if (stateOrProvinceId) + return $http.get('api/location/districts/' + stateOrProvinceId); + + return $http.get('api/location/districts'); + } + + function getWarehouse(id) { + return $http.get('api/warehouses/' + id, null); + } + + function editWarehouse(warehouse) { + return $http.put('api/warehouses/' + warehouse.id, warehouse); + } + + function createWarehouse(warehouse) { + return $http.post('api/warehouses/', warehouse); + } + + function deleteWarehouse(warehouse) { + return $http.delete('api/warehouses/' + warehouse.id, null); + } + } +})(); \ No newline at end of file