from typing import Union, TypedDict from sqlalchemy import select, func, and_, cast, String, case, or_, exists from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import joinedload from database.sipro import * from database.sipro.enums.product import ProductRelationType from database.sipro.models.warehouses import MarketplaceProductFboWarehouseStock class StockData(TypedDict): full_stock: int article: Union[str, int] marketplace_product: MarketplaceProduct product_id: int def get_marketplace_suppliers_and_company_warehouses(marketplace: Marketplace): company = marketplace.company suppliers = set() company_warehouses = set() for warehouse in marketplace.warehouses: for supplier in warehouse.suppliers: if supplier.is_pseudo: continue suppliers.add(supplier) company_warehouses.update(warehouse.company_warehouses) if marketplace.sell_warehouse_products: company_warehouse = company.warehouse if company_warehouse and not company.is_denco: company_warehouses.add(company_warehouse) return list(suppliers), list(company_warehouses) async def get_stocks_data( session: AsyncSession, marketplace: Marketplace, product_ids: Union[list[int], None] = None, reset: bool = False ) -> List[StockData]: if not product_ids: product_ids = [] company = marketplace.company suppliers, company_warehouses = get_marketplace_suppliers_and_company_warehouses(marketplace) supplier_ids = [supplier.id for supplier in suppliers] company_warehouse_ids = [warehouse.id for warehouse in company_warehouses] fbo_warehouse_ids = [fbo_warehouse.id for fbo_warehouse in marketplace.fbo_warehouses] sell_mixes: bool = marketplace.sell_mixes sell_blocks: bool = marketplace.sell_blocks sell_warehouse_products: bool = marketplace.sell_warehouse_products sell_from_price: int = marketplace.sell_from_price prefer_fbo_over_fbs: bool = marketplace.prefer_fbo_over_fbs is_paused = marketplace.is_paused supplier_stock_subquery = ( select( func.greatest( func.sum(SupplierProduct.supplier_stock - SupplierProduct.sold_today), 0 ) .label('supplier_stock'), SupplierProduct.product_id.label('product_id') ) .select_from( SupplierProduct ) .where( SupplierProduct.supplier_id.in_(supplier_ids), SupplierProduct.is_deleted == False ) .group_by( SupplierProduct.product_id ) .subquery() ) warehouse_stock_subquery = ( select( func.count(CompanyWarehouseProduct.is_sold).label('warehouse_stock'), CompanyWarehouseProduct.product_id.label('product_id') ) .select_from( CompanyWarehouseProduct ) .where( CompanyWarehouseProduct.is_sold == False, CompanyWarehouseProduct.company_warehouse_id.in_(company_warehouse_ids) ) .group_by( CompanyWarehouseProduct.product_id ) .subquery() ) mix_stock_first_subquery = ( select( func.sum(func.greatest(SupplierProduct.supplier_stock - SupplierProduct.sold_today, 0)) .label( 'master_stock'), SupplierProduct.product_id.label('product_id') ) .select_from( SupplierProduct ) .where( SupplierProduct.supplier_id.in_(supplier_ids), SupplierProduct.is_deleted == False ) .group_by( SupplierProduct.product_id ) .subquery() ) mix_stock_full_subquery = ( select( func.min(SupplierProduct.in_block).label('mix_stock'), Product.id.label('product_id') ) .select_from( Product ) .join( SupplierProduct ) .join( ProductRelation, Product.id == ProductRelation.slave_product_id ) .join( mix_stock_first_subquery, mix_stock_first_subquery.c.product_id == ProductRelation.master_product_id ) .where( ProductRelation.relation_type == ProductRelationType.MAIN_PRODUCT, mix_stock_first_subquery.c.master_stock > 0 ) .group_by( Product.id ) .subquery() ) is_master_subquery = ( select( Product.id.label("product_id"), # EXISTS возвращает bool, не нужно COUNT > 0 и GROUP BY exists( select(1) .select_from(ProductRelation) .where( and_( ProductRelation.master_product_id == Product.id, ProductRelation.relation_type == ProductRelationType.MAIN_PRODUCT, ) ) ).label("is_master"), ) .subquery() ) in_block_subquery = ( select( SupplierProduct.product_id.label('product_id'), func.min(SupplierProduct.in_block).label('in_block_value') ) .where( SupplierProduct.supplier_id.in_(supplier_ids), (SupplierProduct.supplier_stock - SupplierProduct.sold_today) > 0, SupplierProduct.is_deleted == False ) .group_by( SupplierProduct.product_id ) .subquery() ) slaves_stock_first_subquery = ( select( ProductRelation.master_product_id.label('product_id'), func.sum(func.greatest(SupplierProduct.supplier_stock - SupplierProduct.sold_today)).label('slaves_stock') ) .select_from( ProductRelation ) .join( SupplierProduct, and_( ProductRelation.slave_product_id == SupplierProduct.product_id, ProductRelation.relation_type == ProductRelationType.SAME_MIX ) ) .where( SupplierProduct.supplier_id.in_( supplier_ids ), SupplierProduct.is_deleted == False ) .group_by( ProductRelation.master_product_id ) .subquery() ) slaves_stock_subquery = ( select( Product.id.label('product_id'), slaves_stock_first_subquery.c.slaves_stock.label('slaves_stock') ) .select_from( Product ) .join( slaves_stock_first_subquery, slaves_stock_first_subquery.c.product_id == Product.id ) .subquery() ) fbo_stock_subquery = ( select( MarketplaceProductFboWarehouseStock.marketplace_product_id.label('marketplace_product_id'), func.sum(MarketplaceProductFboWarehouseStock.quantity).label('quantity') ) .select_from( MarketplaceProductFboWarehouseStock ) .join( MarketplaceProduct ) .where( MarketplaceProduct.marketplace_id == marketplace.id, MarketplaceProductFboWarehouseStock.warehouse_id.in_(fbo_warehouse_ids), MarketplaceProductFboWarehouseStock.quantity > 0 ) .group_by( MarketplaceProductFboWarehouseStock.marketplace_product_id ) .subquery() ) stmt = ( select( MarketplaceProduct, func.coalesce(Product.article, cast(Product.denco_article, String)).label('denco_article'), func.coalesce(MarketplaceProduct.mp_price_bought, 0).label('price_purchase'), func.coalesce(supplier_stock_subquery.c.supplier_stock, 0).label('supplier_stock'), case( ( sell_warehouse_products, func.coalesce(warehouse_stock_subquery.c.warehouse_stock, 0) ), else_=0) .label('warehouse_stock'), mix_stock_full_subquery.c.mix_stock.label('mix_stock'), func.coalesce(in_block_subquery.c.in_block_value, 1).label('in_block_value'), func.coalesce(is_master_subquery.c.is_master, False).label('is_master'), func.coalesce(slaves_stock_subquery.c.slaves_stock, 0).label('slaves_stock'), MarketplaceProduct.price_recommended.label('price_recommended'), MarketplaceProduct.is_deleted.label('is_deleted'), func.coalesce(fbo_stock_subquery.c.quantity, 0).label('fbo_stock') ) .select_from( MarketplaceProduct ) .join( Product ) # .options( # joinedload(MarketplaceProduct.product) # ) .where( MarketplaceProduct.marketplace_id == marketplace.id, or_( len(product_ids) == 0, MarketplaceProduct.product_id.in_(product_ids) ) ) .outerjoin( supplier_stock_subquery, supplier_stock_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( warehouse_stock_subquery, warehouse_stock_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( mix_stock_full_subquery, mix_stock_full_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( in_block_subquery, in_block_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( is_master_subquery, is_master_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( slaves_stock_subquery, slaves_stock_subquery.c.product_id == MarketplaceProduct.product_id ) .outerjoin( fbo_stock_subquery, fbo_stock_subquery.c.marketplace_product_id == MarketplaceProduct.id ) ) result = await session.execute(stmt) marketplace_products = result.all() response: List[StockData] = [] for (marketplace_product, denco_article, price_purchase, supplier_stock, warehouse_stock, mix_stock, in_block_value, is_master, slaves_stock, price_recommended, is_deleted, fbo_stock ) in marketplace_products: base_dict: StockData = { 'article': denco_article, 'marketplace_product': marketplace_product, 'product_id': marketplace_product.product_id, 'full_stock': 0 } zero_stock = any([ is_deleted, sell_from_price > price_recommended, is_paused, result, fbo_stock > 0 and prefer_fbo_over_fbs, 45 > company.balance ]) if zero_stock: response.append(base_dict) continue is_mix = mix_stock is not None if not mix_stock: mix_stock = 0 if all([is_mix, slaves_stock > 0]): mix_stock = 0 balance_limit = price_purchase > company.balance if balance_limit: supplier_stock = 0 full_stock = supplier_stock + warehouse_stock if all([not is_mix, not sell_blocks, in_block_value > 1]): full_stock = warehouse_stock if sell_mixes and (not balance_limit): full_stock += mix_stock if (not sell_mixes) and is_master: full_stock = warehouse_stock if (not sell_mixes) and is_mix: full_stock = warehouse_stock full_stock = max([0, full_stock]) base_dict['full_stock'] = full_stock response.append(base_dict) return response