02添加采购订单

添加采购订单

在领域共享项目中添加常量

Domain.Shared 项目中添加文件夹Purchase,在文件夹下添加类PurchaseConsts.cs,添加以下代码:

    public static class PurchaseConsts
    {
        public const int MaxPoNumLength = 20;

        public const int MaxItemNumLength = 50;
        /// <summary>
        /// 订单已存在异常代码
        /// </summary>
        public const string PurchaseOrderAlreadyExists = "PurchaseOrder:00001";
        /// <summary>
        /// 订单不存在异常代码
        /// </summary>
        public const string PurchaseOrderNotExists = "PurchaseOrder:00002";
        /// <summary>
        /// 订单明细已存在异常代码
        /// </summary>
        public const string PurchaseOrderItemAlreadyExists = "PurchaseOrder:00003";
        /// <summary>
        /// 订单名称不存在异常代码
        /// </summary>
        public const string PurchaseOrderItemNotExists = "PurchaseOrder:00004";
    }

添加领域实体

添加实体

Domain 项目中添加文件夹 Purchase,在文件夹中添加实体 PurchaseOrder,添加以下代码:

    public class PurchaseOrder : FullAuditedAggregateRoot<Guid>
    {
        public string PoNum { get; private set; } = string.Empty;

        public DateTime IDate { get; set; }

        public string Buyer { get; set; } = string.Empty;

        public string VenName { get; set; } = string.Empty;

        public decimal TotalQty { get; internal set; }

        public decimal TotalAmount { get; internal set; }

        private PurchaseOrder() { }

        internal PurchaseOrder(Guid id, string poNum, DateTime idate, [NotNull] string buyer, [NotNull] string venName) : base(id)
        {
            SetPoNum(poNum);
            IDate = idate;
            Buyer = buyer;
            VenName = venName;
        }

        internal void UpdatePoNum(string poNum)
        {
            SetPoNum(poNum);
        }

        private void SetPoNum(string poNum)
        {
            PoNum = Check.NotNullOrEmpty(poNum, nameof(poNum), maxLength: PurchaseConsts.MaxPoNumLength);
        }

        internal PurchaseOrderItem CreateNewItem(Guid itemId, string itemNum, decimal qty, decimal price)
        {
            var item = new PurchaseOrderItem(Id, itemId, itemNum, qty, price);
            TotalQty += item.Qty;
            TotalAmount+= item.Amount;
            return item;
        }
    }

添加实体PurchaseOrderItem,添加以下代码:

    public class PurchaseOrderItem : FullAuditedAggregateRoot<Guid>
    {
        public string ItemNum { get; private set; } = string.Empty;
        public decimal Qty { get; private set; }
        public decimal Price { get; private set; }
        public decimal Amount { get; private set; }

        public Guid PurchaseOrderId { get; private set; }

        private PurchaseOrderItem() { }
        internal PurchaseOrderItem(Guid purchaseOrderId,Guid  id,string itemNum,decimal qty,decimal price):base(id)
        {
            PurchaseOrderId= purchaseOrderId;
            SetItemNum(itemNum);
            UpdateQty(qty);
            UpdatePrice(price);
        }

        internal void UpdateQty(decimal qty)
        {
            Qty = qty;
            ResetAmount();
        }

        internal void UpdatePrice(decimal price)
        {
            Price = price;
            ResetAmount();
        }

        internal void ResetAmount()
        {
            Amount = Qty * Price;
        }

        internal void UpdateItemNum([NotNull] string itemNum)
        {
            SetItemNum(itemNum);
        }

        private void SetItemNum([NotNull] string itemNum)
        {
            ItemNum = Check.NotNullOrEmpty(itemNum,nameof(itemNum),maxLength:PurchaseConsts.MaxItemNumLength);
        }
    }

添加领域服务

Domain 项目的文件夹 Purchase下添加类PurchaseOrderManage.cs,并添加以下代码:

    public class PurchaseOrderManage : DomainService
    {
        private readonly IRepository<PurchaseOrder, Guid> _purchaseOrderRepository;
        private readonly IRepository<PurchaseOrderItem, Guid> _purchaseOrderItemRepository;

        public PurchaseOrderManage(IRepository<PurchaseOrder, Guid> purchaseOrderRepository, IRepository<PurchaseOrderItem, Guid> purchaseOrderItemRepository)
        {
            _purchaseOrderRepository = purchaseOrderRepository;
            _purchaseOrderItemRepository = purchaseOrderItemRepository;
        }

        public async Task<PurchaseOrder> CreateAsync([NotNull] string poNum, [NotNull] DateTime iDate, [NotNull] string buyer, [NotNull] string venName)
        {
            Check.NotNullOrWhiteSpace(poNum, nameof(poNum));
            Check.NotNull(iDate, nameof(iDate));
            Check.NotNullOrWhiteSpace(buyer, nameof(buyer));
            Check.NotNullOrWhiteSpace(venName, nameof(venName));

            var po = await _purchaseOrderRepository.FindAsync(p => p.PoNum.Equals(poNum));

            if (po != null)
            {
                throw new PurchaseOrderAlreadyExistsException(poNum);
            }

            return new PurchaseOrder(GuidGenerator.Create(), poNum, iDate, buyer, venName);
        }

        public async Task UpdatePoNum([NotNull] PurchaseOrder purchaseOrder, [NotNull] string poNum)
        {
            Check.NotNull(purchaseOrder, nameof(purchaseOrder));
            Check.NotNullOrWhiteSpace(poNum, nameof(poNum));

            var exstingPurchaseOrder = await _purchaseOrderRepository.FindAsync(p => p.PoNum.Equals(poNum));

            if (exstingPurchaseOrder != null && exstingPurchaseOrder.Id != purchaseOrder.Id)
            {
                throw new PurchaseOrderAlreadyExistsException(poNum);
            }

            purchaseOrder.UpdatePoNum(poNum);
        }

        public PurchaseOrderItem AddPurchaseOrderItem([NotNull] PurchaseOrder purchaseOrder, string itemNum, decimal qty, decimal price)
        {
            Check.NotNull(purchaseOrder, nameof(purchaseOrder));
            Check.NotNullOrWhiteSpace(itemNum, nameof(itemNum));

            var item = purchaseOrder.CreateNewItem(GuidGenerator.Create(), itemNum, qty, price);

            return item;
        }

        public async Task<PurchaseOrder> UpdatePurchaseOrderItem([NotNull] PurchaseOrderItem purchaseOrderItem, string itemNum, decimal qty, decimal price)
        {
            Check.NotNull(purchaseOrderItem, nameof(purchaseOrderItem));

            var differenceQty = qty - purchaseOrderItem.Qty;
            var diferenceAmount = qty * price - purchaseOrderItem.Amount;

            purchaseOrderItem.UpdateItemNum(itemNum);
            purchaseOrderItem.UpdateQty(qty);
            purchaseOrderItem.UpdatePrice(price);

            var po = await _purchaseOrderRepository.FindAsync(purchaseOrderItem.PurchaseOrderId);

            po.TotalQty += differenceQty;
            po.TotalAmount += diferenceAmount;
            return po;
        }

        public async Task<PurchaseOrder> RemovePurchaseOrderItem([NotNull] PurchaseOrderItem purchaseOrderItem)
        {
            Check.NotNull(purchaseOrderItem, nameof(purchaseOrderItem));
            var po = await _purchaseOrderRepository.FindAsync(purchaseOrderItem.PurchaseOrderId);

            po.TotalQty -= purchaseOrderItem.Qty;
            po.TotalAmount -= purchaseOrderItem.Amount;

            return po;
        }

        public async Task ResetPurchaseOrderTotalQtyTotalAmountAsync([NotNull] PurchaseOrder purchaseOrder)
        {
            Check.NotNull(purchaseOrder, nameof(purchaseOrder));
            await ResetPurchaseOrderTotalQtyAsync(purchaseOrder);
            await ResetPurchaseOrderTotalAmountAsync(purchaseOrder);

        }
        public async Task ResetPurchaseOrderTotalQtyAsync([NotNull] PurchaseOrder purchaseOrder)
        {
            Check.NotNull(purchaseOrder, nameof(purchaseOrder));
            var query = await _purchaseOrderItemRepository.GetQueryableAsync();
            var totalQty = query
                .Where(p=>p.PurchaseOrderId.Equals(purchaseOrder.Id))
                .Sum(p => p.Qty);

            purchaseOrder.TotalQty = totalQty;
        }
        public async Task ResetPurchaseOrderTotalAmountAsync([NotNull] PurchaseOrder purchaseOrder)
        {
            Check.NotNull(purchaseOrder, nameof(purchaseOrder));
            var query = await _purchaseOrderItemRepository.GetQueryableAsync();
            var totalAmount = query
                .Where(p => p.PurchaseOrderId.Equals(purchaseOrder.Id))
                .Sum(p => p.Amount);

            purchaseOrder.TotalAmount = totalAmount;

        }
    }

添加异常类PurchaseOrderAlreadyExistsException.csPurchaseOrderNotExistsException.cs,代码如下:

    public class PurchaseOrderAlreadyExistsException : BusinessException
    {
        public PurchaseOrderAlreadyExistsException(string poNum) : base(PurchaseConsts.PurchaseOrderAlreadyExists)
        {
            WithData("poNum", poNum);
        }
    }
    public class PurchaseOrderNotExistsException: BusinessException
    {
        public PurchaseOrderNotExistsException(Guid id) : base(PurchaseConsts.PurchaseOrderNotExists)
        {
            WithData("id", id);
        }
    }

数据库配置

添加数据库配置常量

在项目EntityFrameworkCore中添加类MyDbContextConsts.cs,添加以下代码:

    public static class MyDbContextConsts
    {
        public const string DbTablePrefix = "Study";

        public const string DbSchema = null;
    }

添加数据库上下文

在项目EntityFrameworkCore中添加类MyDbContext.cs,添加以下代码:

    [ConnectionStringName("Default")]
    public class MyDbContext : AbpDbContext<MyDbContext>
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }

        public DbSet<PurchaseOrder> PurchaseOrders { get; set; } 
        public DbSet<PurchaseOrderItem> PurchaseOrderItems { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<PurchaseOrder>(p =>
            {
                p.ToTable(MyDbContextConsts.DbTablePrefix + "_PurchaseOrders", MyDbContextConsts.DbSchema);
                p.ConfigureByConvention();
                p.Property(x => x.PoNum)
                    .IsRequired()
                    .HasMaxLength(PurchaseConsts.MaxPoNumLength);
            });

            modelBuilder.Entity<PurchaseOrderItem>(p =>
            {
                p.ToTable(MyDbContextConsts.DbTablePrefix+"_PurchaseOrderItems",MyDbContextConsts.DbSchema);
                p.ConfigureByConvention();
                p.Property(x=>x.ItemNum)
                    .IsRequired()
                    .HasMaxLength(PurchaseConsts.MaxItemNumLength);

                p.HasOne<PurchaseOrder>()
                    .WithMany()
                    .HasForeignKey(x => x.PurchaseOrderId)
                    .IsRequired();
            });
        }
    }

EntityFrameworkCoreModule.cs添加以下代码:

            // 添加数据库上下文
            context.Services.AddAbpDbContext<MyDbContext>(options =>
            {
                options.AddDefaultRepositories(includeAllEntities: true);
            });

完整代码:

    [DependsOn(
        typeof(AbpEntityFrameworkCoreSqlServerModule)
    )]
    public class EntityFrameworkCoreModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            base.ConfigureServices(context);
            // 添加数据库上下文
            context.Services.AddAbpDbContext<MyDbContext>(options =>
            {
                options.AddDefaultRepositories(includeAllEntities: true);
            });
            // 配置使用 SqlServer
            Configure<AbpDbContextOptions>(options =>
            {
                options.UseSqlServer();
            });
        }
    }

应用层约定

添加数据传输对象

    public class CreatePurchaseOrderDto
    {
        [Required]
        [StringLength(PurchaseConsts.MaxPoNumLength)]
        public string PoNum { get; set; }
        [Required]
        public DateTime IDate { get; set; }
        [Required]
        public string Buyer { get; set; }
        [Required]
        public string VenName { get; set; }
    }
    public class CreatePurchaseOrderAndDetailDto: CreatePurchaseOrderDto
    {
        public IList<DetailItemDto> Details { get; set; }

        public class DetailItemDto
        {
            [Required]
            [StringLength(PurchaseConsts.MaxItemNumLength)]
            public string ItemNum { get; set; }
            [Required]
            public decimal Qty { get; set; }
            [Required]
            public decimal Price { get; set; }
        }
    }
    public class CreatePurchaseOrderDetailDto
    {
        [Required]
        public Guid PurchaseOrderId { get; set; }

        [Required]
        [StringLength(PurchaseConsts.MaxItemNumLength)]
        public string ItemNum { get; set; }
        [Required]
        public decimal Qty { get; set; }
        [Required]
        public decimal Price { get; set; }
    }
    public class GetPurchaseOrderListDto:PagedAndSortedResultRequestDto
    {
        public DateTime? BeginIDate { get; set; }
        public DateTime? EndIDate { get; set;}
        public string? VenName { get; set; }
        public string? PoNum { get; set; }
        public string? Buyer { get;}
    }
    public class PurchaseOrderDto
    {
        public Guid Id { get; set; }

        public string PoNum { get; set; }

        public DateTime IDate { get; set; }

        public string Buyer { get; set; }
        public string VenName { get; set; }
        public decimal TotalQty { get; set; }
        public decimal TotalAmount { get; set; }

    }
    public class PurchaseOrderDetailDto
    {
        public Guid Id { get; set; }
        public string PonNum { get; set; }
        public string ItemNum { get; set; }
        public decimal Qty { get; set; }
        public decimal Price { get; set; }
        public decimal Amount { get; set; }
        public Guid PurchaseOrderId { get; set; }
    }
    public class PurchaseOrderAndDetailDto: PurchaseOrderDto
    {
        public IList<PurchaseOrderDetailDto> Details { get; set; }
    }
    public class UpdatePurchaseOrderDetailDto
    {
        [Required]
        [StringLength(PurchaseConsts.MaxItemNumLength)]
        public string ItemNum { get; set; }
        [Required]
        public decimal Qty { get; set; }
        [Required]
        public decimal Price { get; set; }
    }
    public class UpdatePurchaseOrderDto
    {
        [Required]
        [StringLength(PurchaseConsts.MaxPoNumLength)]
        public string PoNum { get; set; }
        [Required]
        public DateTime IDate { get; set; }
        [Required]
        public string Buyer { get; set; }
        [Required]
        public string VenName { get; set; }
    }

添加应用服务接口

    public interface IPurchaseOrderService:IApplicationService
    {
        Task<PurchaseOrderAndDetailDto> GetAsync(Guid id);

        Task<PurchaseOrderDetailDto> GetDetailAsync(Guid id);

        Task<PagedResultDto<PurchaseOrderDto>> GetListAsync(GetPurchaseOrderListDto input);

        Task<List<PurchaseOrderDetailDto>> GetDetailListAsync(Guid id);

        Task<PurchaseOrderAndDetailDto> CreatePurchaseOrderAndDetailAsync(CreatePurchaseOrderAndDetailDto createPurchaseOrderAndDetail);

        Task<PurchaseOrderDto> CreatePurchaseOrderAsync(CreatePurchaseOrderDto createPurchaseOrder);

        Task UpdatePurchaseOrderAsync(Guid id,UpdatePurchaseOrderDto updatePurchaseOrder);

        Task DeletePurchaseOrderAsync(Guid id);

        Task<PurchaseOrderDetailDto> CreatePurchaseOrderDetail(CreatePurchaseOrderDetailDto createPurchaseOrderDetail);

        Task UpdatePurchaseOrderDetailAsync(Guid id, UpdatePurchaseOrderDetailDto updatePurchaseOrder);

        Task DeletePurchaseOrderDetailAsync(Guid id);
    }

应用层

实现应用服务

    /// <summary>
    /// 应用层服务基类
    /// </summary>
    public abstract class ApplicationAppService:ApplicationService
    {
        protected ApplicationAppService()
        {
            // 配置本地资源
            LocalizationResource = typeof(MyLocalizationResource);
        }
    }
    public class PurchaseOrderService : ApplicationAppService, IPurchaseOrderService
    {
        private readonly IRepository<Domain.Purchase.PurchaseOrder, Guid> _repository;
        private readonly IRepository<PurchaseOrderItem, Guid> _itemRepository;
        private readonly PurchaseOrderManage _purchaseOrderManage;

        public PurchaseOrderService(IRepository<Domain.Purchase.PurchaseOrder, Guid> repository, IRepository<PurchaseOrderItem, Guid> itemRepository, PurchaseOrderManage purchaseOrderManage)
        {
            _repository = repository;
            _itemRepository = itemRepository;
            _purchaseOrderManage = purchaseOrderManage;
        }

        public async Task<PurchaseOrderAndDetailDto> CreatePurchaseOrderAndDetailAsync(CreatePurchaseOrderAndDetailDto createPurchaseOrderAndDetail)
        {
            Check.NotNull(createPurchaseOrderAndDetail, nameof(createPurchaseOrderAndDetail));
            var details = new List<Domain.Purchase.PurchaseOrderItem>();
            var po = await _purchaseOrderManage.CreateAsync(createPurchaseOrderAndDetail.PoNum, createPurchaseOrderAndDetail.IDate, createPurchaseOrderAndDetail.Buyer, createPurchaseOrderAndDetail.VenName);

            foreach (var item in createPurchaseOrderAndDetail.Details)
            {
                details.Add(_purchaseOrderManage.AddPurchaseOrderItem(po, item.ItemNum, item.Qty, item.Price));
            }

            /**
                对资源库数据的多次修改是在一个事务中进行的,详情请见
                https://docs.abp.io/zh-Hans/abp/latest/Unit-Of-Work
             */
            await _repository.InsertAsync(po);
            await _itemRepository.InsertManyAsync(details);
            await _purchaseOrderManage.ResetPurchaseOrderTotalQtyTotalAmountAsync(po);
            await _repository.UpdateAsync(po);

            // 在单元工作结束而没有任何异常是,所有的数据库更改都会自动保存,因此无需手动调用 SaveChangesAsync
            //await UnitOfWorkManager.Current.SaveChangesAsync();
            return await GetAsync(po.Id);
        }

        public async Task<PurchaseOrderDto> CreatePurchaseOrderAsync(CreatePurchaseOrderDto createPurchaseOrder)
        {
            Check.NotNull(createPurchaseOrder, nameof(createPurchaseOrder));
            var po = await _purchaseOrderManage.CreateAsync(createPurchaseOrder.PoNum, createPurchaseOrder.IDate, createPurchaseOrder.Buyer, createPurchaseOrder.VenName);
            po = await _repository.InsertAsync(po);
            return ObjectMapper.Map<Domain.Purchase.PurchaseOrder,PurchaseOrderDto>(po);
        }

        public async Task<PurchaseOrderDetailDto> CreatePurchaseOrderDetail(CreatePurchaseOrderDetailDto createPurchaseOrderDetail)
        {
            Check.NotNull(createPurchaseOrderDetail, nameof(createPurchaseOrderDetail));
            var po = await GetPurchaseOrderAndChecked(createPurchaseOrderDetail.PurchaseOrderId);
            var item = _purchaseOrderManage.AddPurchaseOrderItem(po, createPurchaseOrderDetail.ItemNum, createPurchaseOrderDetail.Qty, createPurchaseOrderDetail.Price);
            await _itemRepository.InsertAsync(item);
            await _purchaseOrderManage.ResetPurchaseOrderTotalQtyTotalAmountAsync(po);
            await _repository.UpdateAsync(po);

            return ObjectMapper.Map<PurchaseOrderItem, PurchaseOrderDetailDto>(item);
        }

        public async Task DeletePurchaseOrderAsync(Guid id)
        {
            await _repository.DeleteAsync(id);
        }

        public async Task DeletePurchaseOrderDetailAsync(Guid id)
        {
            var item = await GetPurchaseOrderItemAndChecked(id);
            var po = await GetPurchaseOrderAndChecked(item.PurchaseOrderId);
            await _itemRepository.DeleteAsync(id);
            await _purchaseOrderManage.ResetPurchaseOrderTotalQtyTotalAmountAsync(po);
        }

        public async Task<PurchaseOrderAndDetailDto> GetAsync(Guid id)
        {
            var purchaseOrder = await _repository.GetAsync(id);
            var dto = ObjectMapper.Map<Domain.Purchase.PurchaseOrder, PurchaseOrderAndDetailDto>(purchaseOrder);
            dto.Details = await GetDetailListAsync(dto.Id);

            return dto;
        }

        public async Task<PurchaseOrderDetailDto> GetDetailAsync(Guid id)
        {
            var detail = await _itemRepository.FindAsync(id);
            return ObjectMapper.Map<PurchaseOrderItem, PurchaseOrderDetailDto>(detail);
        }

        public async Task<List<PurchaseOrderDetailDto>> GetDetailListAsync(Guid id)
        {
            var query = await _itemRepository.GetQueryableAsync();
            var details = query.Where(x => x.PurchaseOrderId == id).ToList();

            return ObjectMapper.Map<List<PurchaseOrderItem>, List<PurchaseOrderDetailDto>>(details);
        }

        public async Task<PagedResultDto<PurchaseOrderDto>> GetListAsync(GetPurchaseOrderListDto input)
        {
            if (input.Sorting.IsNullOrWhiteSpace())
            {
                input.Sorting = nameof(Domain.Purchase.PurchaseOrder.PoNum);
            }
            var query = await _repository.GetQueryableAsync();
            query = query.WhereIf(input.BeginIDate.HasValue, p => p.IDate >= input.BeginIDate)
                .WhereIf(input.EndIDate.HasValue, p => p.IDate <= input.EndIDate)
                .WhereIf(!string.IsNullOrWhiteSpace(input.VenName), p => p.VenName.Contains(input.VenName == null ? string.Empty : input.VenName))
                .WhereIf(!string.IsNullOrWhiteSpace(input.PoNum), p => p.PoNum.Contains(input.PoNum == null ? string.Empty : input.PoNum))
                .WhereIf(!string.IsNullOrWhiteSpace(input.Buyer), p => p.PoNum.Contains(input.Buyer == null ? string.Empty : input.Buyer))
                .OrderBy(input.Sorting)
                .Skip(input.SkipCount)
                .Take(input.MaxResultCount);

            var queryResult = await AsyncExecuter.ToListAsync(query);
            var totalCount = await _repository.GetCountAsync();

            return new PagedResultDto<PurchaseOrderDto>(totalCount, ObjectMapper.Map<List<Domain.Purchase.PurchaseOrder>, List<PurchaseOrderDto>>(queryResult));
        }

        public async Task UpdatePurchaseOrderAsync(Guid id, UpdatePurchaseOrderDto updatePurchaseOrder)
        {
            var po = await GetPurchaseOrderAndChecked(id);
            po.Buyer = updatePurchaseOrder.Buyer;
            po.VenName = updatePurchaseOrder.VenName;
            po.IDate = updatePurchaseOrder.IDate;
            await _purchaseOrderManage.UpdatePoNum(po, updatePurchaseOrder.PoNum);
            _ = await _repository.UpdateAsync(po);
        }

        public async Task UpdatePurchaseOrderDetailAsync(Guid id, UpdatePurchaseOrderDetailDto updatePurchaseOrder)
        {
            var item = await GetPurchaseOrderItemAndChecked(id);
            var po = await GetPurchaseOrderAndChecked(item.PurchaseOrderId);
            await _purchaseOrderManage.UpdatePurchaseOrderItem(item, updatePurchaseOrder.ItemNum, updatePurchaseOrder.Qty, updatePurchaseOrder.Price);
            await _itemRepository.UpdateAsync(item);

            await _purchaseOrderManage.ResetPurchaseOrderTotalQtyTotalAmountAsync(po);
            await _repository.UpdateAsync(po);
        }

        private async Task<Domain.Purchase.PurchaseOrder> GetPurchaseOrderAndChecked(Guid id)
        {
            var po = await _repository.FindAsync(id);
            if (po == null)
            {
                throw new PurchaseOrderNotExistsException(id);
            }

            return po;
        }

        private async Task<Domain.Purchase.PurchaseOrderItem> GetPurchaseOrderItemAndChecked(Guid id)
        {
            var item = await _itemRepository.FindAsync(id);
            if (item == null)
            {
                var ex = new BusinessException(PurchaseConsts.PurchaseOrderItemNotExists);
                ex.WithData("id", id);
                throw ex;
            }
            return item;
        }
    }
posted @ 2022-12-30 18:28  $("#阿飞")  阅读(110)  评论(0编辑  收藏  举报